diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/DrtWithExtensionsConfigGroup.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/DrtWithExtensionsConfigGroup.java index 7a38e5626ee..0db7bda1d6a 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/DrtWithExtensionsConfigGroup.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/DrtWithExtensionsConfigGroup.java @@ -19,13 +19,14 @@ package org.matsim.contrib.drt.extension; +import java.util.Optional; + +import javax.annotation.Nullable; + import org.matsim.contrib.drt.extension.companions.DrtCompanionParams; import org.matsim.contrib.drt.extension.operations.DrtOperationsParams; import org.matsim.contrib.drt.run.DrtConfigGroup; -import javax.annotation.Nullable; -import java.util.Optional; - /** * @author Steffen Axer * @@ -56,5 +57,5 @@ public Optional getDrtCompanionParams() { public Optional getDrtOperationsParams() { return Optional.ofNullable(drtOperationsParams); } - + } diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/EDrtActionCreator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/EDrtActionCreator.java index dcd376f1cef..2e90569768c 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/EDrtActionCreator.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/EDrtActionCreator.java @@ -19,10 +19,8 @@ package org.matsim.contrib.drt.extension.edrt; -import org.matsim.contrib.drt.vrpagent.DrtActionCreator; +import org.matsim.contrib.drt.extension.edrt.schedule.EDrtChargingTask; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; -import org.matsim.contrib.dvrp.passenger.PassengerHandler; -import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.dvrp.schedule.DriveTask; import org.matsim.contrib.dvrp.schedule.Task; import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker; @@ -33,7 +31,6 @@ import org.matsim.contrib.dvrp.vrpagent.VrpLeg; import org.matsim.contrib.dynagent.DynAction; import org.matsim.contrib.dynagent.DynAgent; -import org.matsim.contrib.drt.extension.edrt.schedule.EDrtChargingTask; import org.matsim.contrib.evrp.ChargingActivity; import org.matsim.contrib.evrp.ChargingTask; import org.matsim.contrib.evrp.EvDvrpVehicle; @@ -45,12 +42,12 @@ * @author michalm */ public class EDrtActionCreator implements VrpAgentLogic.DynActionCreator { - private final DrtActionCreator drtActionCreator; + private final VrpAgentLogic.DynActionCreator delegate; private final MobsimTimer timer; - public EDrtActionCreator(PassengerHandler passengerHandler, MobsimTimer timer, DvrpConfigGroup dvrpCfg) { + public EDrtActionCreator(VrpAgentLogic.DynActionCreator delegate, MobsimTimer timer) { this.timer = timer; - drtActionCreator = new DrtActionCreator(passengerHandler, v -> createLeg(dvrpCfg.mobsimMode, v, timer)); + this.delegate = delegate; } @Override @@ -60,7 +57,7 @@ public DynAction createAction(DynAgent dynAgent, DvrpVehicle vehicle, double now task.initTaskTracker(new OfflineETaskTracker((EvDvrpVehicle)vehicle, timer)); return new ChargingActivity((ChargingTask)task); } else { - DynAction dynAction = drtActionCreator.createAction(dynAgent, vehicle, now); + DynAction dynAction = delegate.createAction(dynAgent, vehicle, now); if (task.getTaskTracker() == null) { task.initTaskTracker(new OfflineETaskTracker((EvDvrpVehicle)vehicle, timer)); } @@ -68,7 +65,7 @@ public DynAction createAction(DynAgent dynAgent, DvrpVehicle vehicle, double now } } - private static VrpLeg createLeg(String mobsimMode, DvrpVehicle vehicle, MobsimTimer timer) { + public static VrpLeg createLeg(String mobsimMode, DvrpVehicle vehicle, MobsimTimer timer) { DriveTask driveTask = (DriveTask)vehicle.getSchedule().getCurrentTask(); VrpLeg leg = new VrpLeg(mobsimMode, driveTask.getPath()); OnlineDriveTaskTracker onlineTracker = new OnlineDriveTaskTrackerImpl(vehicle, leg, diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/optimizer/EDrtVehicleDataEntryFactory.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/optimizer/EDrtVehicleDataEntryFactory.java index 7174f562e15..f552fb3ecae 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/optimizer/EDrtVehicleDataEntryFactory.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/optimizer/EDrtVehicleDataEntryFactory.java @@ -20,19 +20,18 @@ import java.util.List; -import org.matsim.contrib.drt.optimizer.VehicleEntry; +import org.matsim.contrib.drt.extension.edrt.schedule.EDrtChargingTask; import org.matsim.contrib.drt.optimizer.VehicleDataEntryFactoryImpl; -import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.drt.optimizer.VehicleEntry; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.schedule.Schedule; import org.matsim.contrib.dvrp.schedule.Schedule.ScheduleStatus; import org.matsim.contrib.dvrp.schedule.Task; import org.matsim.contrib.dvrp.schedule.Task.TaskStatus; -import org.matsim.contrib.drt.extension.edrt.schedule.EDrtChargingTask; +import org.matsim.contrib.ev.fleet.Battery; import org.matsim.contrib.evrp.ETask; import org.matsim.contrib.evrp.EvDvrpVehicle; import org.matsim.contrib.evrp.tracker.ETaskTracker; -import org.matsim.contrib.ev.fleet.Battery; import com.google.inject.Provider; @@ -52,17 +51,13 @@ public EVehicleEntry(VehicleEntry entry, double socBeforeFinalStay) { private final double minimumRelativeSoc; private final VehicleDataEntryFactoryImpl entryFactory; - public EDrtVehicleDataEntryFactory(DrtConfigGroup drtCfg, double minimumRelativeSoc) { + public EDrtVehicleDataEntryFactory(double minimumRelativeSoc) { this.minimumRelativeSoc = minimumRelativeSoc; - entryFactory = new VehicleDataEntryFactoryImpl(drtCfg); + entryFactory = new VehicleDataEntryFactoryImpl(); } @Override public VehicleEntry create(DvrpVehicle vehicle, double currentTime) { - if (entryFactory.isNotEligibleForRequestInsertion(vehicle, currentTime)) { - return null; - } - Schedule schedule = vehicle.getSchedule(); int taskCount = schedule.getTaskCount(); if (taskCount > 1) { @@ -100,17 +95,15 @@ public VehicleEntry create(DvrpVehicle vehicle, double currentTime) { } public static class EDrtVehicleDataEntryFactoryProvider implements Provider { - private final DrtConfigGroup drtCfg; private final double minimumRelativeSoc; - public EDrtVehicleDataEntryFactoryProvider(DrtConfigGroup drtCfg, double minimumRelativeSoc) { - this.drtCfg = drtCfg; + public EDrtVehicleDataEntryFactoryProvider(double minimumRelativeSoc) { this.minimumRelativeSoc = minimumRelativeSoc; } @Override public EDrtVehicleDataEntryFactory get() { - return new EDrtVehicleDataEntryFactory(drtCfg, minimumRelativeSoc); + return new EDrtVehicleDataEntryFactory(minimumRelativeSoc); } } } diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/EDrtModeOptimizerQSimModule.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/EDrtModeOptimizerQSimModule.java index d84aa00e156..71ee5abe1c1 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/EDrtModeOptimizerQSimModule.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/EDrtModeOptimizerQSimModule.java @@ -44,21 +44,24 @@ import org.matsim.contrib.drt.optimizer.insertion.UnplannedRequestInserter; import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingStrategy; import org.matsim.contrib.drt.passenger.DrtOfferAcceptor; +import org.matsim.contrib.drt.prebooking.PrebookingActionCreator; import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.schedule.DrtTaskFactory; import org.matsim.contrib.drt.scheduler.DefaultRequestInsertionScheduler; import org.matsim.contrib.drt.scheduler.DrtScheduleInquiry; import org.matsim.contrib.drt.scheduler.EmptyVehicleRelocator; import org.matsim.contrib.drt.scheduler.RequestInsertionScheduler; +import org.matsim.contrib.drt.stops.PassengerStopDurationProvider; import org.matsim.contrib.drt.stops.StopTimeCalculator; +import org.matsim.contrib.drt.vrpagent.DrtActionCreator; import org.matsim.contrib.dvrp.fleet.Fleet; import org.matsim.contrib.dvrp.optimizer.VrpOptimizer; -import org.matsim.contrib.dvrp.passenger.PassengerHandler; import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.dvrp.run.DvrpModes; import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater; import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic; +import org.matsim.contrib.dvrp.vrpagent.VrpLegFactory; import org.matsim.contrib.ev.infrastructure.ChargingInfrastructure; import org.matsim.contrib.ev.infrastructure.ChargingInfrastructureUtils; import org.matsim.core.api.experimental.events.EventsManager; @@ -69,6 +72,7 @@ import org.matsim.core.router.util.TravelTime; import com.google.inject.Inject; +import com.google.inject.Singleton; /** * @author Michal Maciejewski (michalm) @@ -88,11 +92,13 @@ protected void configureQSim() { getter.getModal(EmptyVehicleChargingScheduler.class)))); bindModal(DefaultDrtOptimizer.class).toProvider(modalProvider( - getter -> new DefaultDrtOptimizer(drtCfg, getter.getModal(Fleet.class), getter.get(MobsimTimer.class), + getter -> { + return new DefaultDrtOptimizer(drtCfg, getter.getModal(Fleet.class), getter.get(MobsimTimer.class), getter.getModal(DepotFinder.class), getter.getModal(RebalancingStrategy.class), getter.getModal(DrtScheduleInquiry.class), getter.getModal(ScheduleTimingUpdater.class), getter.getModal(EmptyVehicleRelocator.class), getter.getModal(UnplannedRequestInserter.class), - getter.getModal(DrtRequestInsertionRetryQueue.class)))).asEagerSingleton(); + getter.getModal(DrtRequestInsertionRetryQueue.class)); + })).asEagerSingleton(); bindModal(ChargingInfrastructure.class).toProvider(modalProvider( getter -> ChargingInfrastructureUtils.createModalNetworkChargers(getter.get(ChargingInfrastructure.class ), @@ -129,7 +135,8 @@ public EmptyVehicleChargingScheduler get() { getter.getModal(RequestInsertionScheduler.class), getter.getModal(VehicleEntry.EntryFactory.class), getter.getModal(DrtInsertionSearch.class), getter.getModal(DrtRequestInsertionRetryQueue.class), getter.getModal(DrtOfferAcceptor.class), - getter.getModal(QSimScopeForkJoinPoolHolder.class).getPool()))).asEagerSingleton(); + getter.getModal(QSimScopeForkJoinPoolHolder.class).getPool(), + getter.getModal(PassengerStopDurationProvider.class)))).asEagerSingleton(); bindModal(InsertionCostCalculator.class).toProvider(modalProvider( getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class)))); @@ -163,11 +170,12 @@ public EmptyVehicleRelocator get() { bindModal(DrtScheduleInquiry.class).to(DrtScheduleInquiry.class).asEagerSingleton(); + boolean scheduleWaitBeforeDrive = drtCfg.getPrebookingParams().map(p -> p.scheduleWaitBeforeDrive).orElse(false); bindModal(RequestInsertionScheduler.class).toProvider(modalProvider( getter -> new DefaultRequestInsertionScheduler(getter.getModal(Fleet.class), getter.get(MobsimTimer.class), getter.getModal(TravelTime.class), getter.getModal(ScheduleTimingUpdater.class), getter.getModal(DrtTaskFactory.class), - getter.getModal(StopTimeCalculator.class)))) + getter.getModal(StopTimeCalculator.class), scheduleWaitBeforeDrive))) .asEagerSingleton(); bindModal(DrtOfferAcceptor.class).toInstance(DrtOfferAcceptor.DEFAULT_ACCEPTOR); @@ -176,10 +184,26 @@ public EmptyVehicleRelocator get() { getter -> new ScheduleTimingUpdater(getter.get(MobsimTimer.class), new EDrtStayTaskEndTimeCalculator(getter.getModal(StopTimeCalculator.class))))).asEagerSingleton(); - bindModal(VrpAgentLogic.DynActionCreator.class).toProvider(modalProvider( - getter -> new EDrtActionCreator(getter.getModal(PassengerHandler.class), getter.get(MobsimTimer.class), - getter.get(DvrpConfigGroup.class)))).asEagerSingleton(); - + bindModal(VrpLegFactory.class).toProvider(modalProvider(getter -> { + DvrpConfigGroup dvrpCfg = getter.get(DvrpConfigGroup.class); + MobsimTimer timer = getter.get(MobsimTimer.class); + + // Makes basic DrtActionCreator create legs with consumption tracker + return v -> EDrtActionCreator.createLeg(dvrpCfg.mobsimMode, v, timer); + })).in(Singleton.class); + + bindModal(EDrtActionCreator.class).toProvider(modalProvider(getter -> { + VrpAgentLogic.DynActionCreator delegate = drtCfg.getPrebookingParams().isPresent() + ? getter.getModal(PrebookingActionCreator.class) + : getter.getModal(DrtActionCreator.class); + + // EDrtActionCreator wraps around delegate and initializes consumption trackers + // + adds ChargingActivity + return new EDrtActionCreator(delegate, getter.get(MobsimTimer.class)); + })).asEagerSingleton(); + + bindModal(VrpAgentLogic.DynActionCreator.class).to(modalKey(EDrtActionCreator.class)); + bindModal(VrpOptimizer.class).to(modalKey(DrtOptimizer.class)); } } diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenario.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenario.java index 741a03e52b2..741c4047786 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenario.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenario.java @@ -54,14 +54,14 @@ public static void run(URL configUrl, boolean otfvis) { new OTFVisConfigGroup(), new EvConfigGroup()), otfvis); } - public static void run(Config config, boolean otfvis) { + public static Controler createControler(Config config, boolean otfvis) { Controler controler = EDrtControlerCreator.createControler(config, otfvis); for (DrtConfigGroup drtCfg : MultiModeDrtConfigGroup.get(config).getModalElements()) { controler.addOverridingModule(new AbstractDvrpModeModule(drtCfg.getMode()) { @Override public void install() { bind(EDrtVehicleDataEntryFactoryProvider.class).toInstance( - new EDrtVehicleDataEntryFactoryProvider(drtCfg, MIN_RELATIVE_SOC)); + new EDrtVehicleDataEntryFactoryProvider(MIN_RELATIVE_SOC)); } }); } @@ -75,6 +75,11 @@ public void install() { bind(TemperatureService.class).toInstance(linkId -> TEMPERATURE); } }); - controler.run(); + + return controler; + } + + public static void run(Config config, boolean otfvis) { + createControler(config, otfvis).run(); } } diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/run/ShiftEDrtModeOptimizerQSimModule.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/run/ShiftEDrtModeOptimizerQSimModule.java index 4fc1aafaad1..ab537fd439d 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/run/ShiftEDrtModeOptimizerQSimModule.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/run/ShiftEDrtModeOptimizerQSimModule.java @@ -1,6 +1,7 @@ package org.matsim.contrib.drt.extension.operations.eshifts.run; import org.matsim.api.core.v01.network.Network; +import org.matsim.contrib.drt.extension.edrt.EDrtActionCreator; import org.matsim.contrib.drt.extension.edrt.optimizer.EDrtVehicleDataEntryFactory; import org.matsim.contrib.drt.extension.edrt.schedule.EDrtTaskFactoryImpl; import org.matsim.contrib.drt.extension.edrt.scheduler.EmptyVehicleChargingScheduler; @@ -20,23 +21,29 @@ import org.matsim.contrib.drt.extension.operations.shifts.dispatcher.DrtShiftDispatcher; import org.matsim.contrib.drt.extension.operations.shifts.dispatcher.DrtShiftDispatcherImpl; import org.matsim.contrib.drt.extension.operations.shifts.optimizer.ShiftVehicleDataEntryFactory; +import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftDrtActionCreator; import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftDrtTaskFactory; import org.matsim.contrib.drt.extension.operations.shifts.scheduler.ShiftTaskScheduler; import org.matsim.contrib.drt.extension.operations.shifts.shift.DrtShifts; import org.matsim.contrib.drt.optimizer.VehicleEntry; +import org.matsim.contrib.drt.prebooking.PrebookingActionCreator; import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.schedule.DrtTaskFactory; +import org.matsim.contrib.drt.vrpagent.DrtActionCreator; import org.matsim.contrib.dvrp.fleet.Fleet; import org.matsim.contrib.dvrp.passenger.PassengerHandler; import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic; +import org.matsim.contrib.dvrp.vrpagent.VrpLegFactory; import org.matsim.contrib.ev.infrastructure.ChargingInfrastructure; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimTimer; import org.matsim.core.router.costcalculators.TravelDisutilityFactory; import org.matsim.core.router.util.TravelTime; +import com.google.inject.Singleton; + /** * @author nkuehnel / MOIA */ @@ -69,7 +76,7 @@ drtShiftParams, new EDrtShiftStartLogic(new DefaultShiftStartLogic()), new EDrtAssignShiftToVehicleLogic(new DefaultAssignShiftToVehicleLogic(drtShiftParams), drtShiftParams)), getter.getModal(Fleet.class)))).asEagerSingleton(); - bindModal(VehicleEntry.EntryFactory.class).toProvider(modalProvider(getter -> new ShiftVehicleDataEntryFactory(new EDrtVehicleDataEntryFactory(drtCfg, 0)))).asEagerSingleton(); + bindModal(VehicleEntry.EntryFactory.class).toProvider(modalProvider(getter -> new ShiftVehicleDataEntryFactory(new EDrtVehicleDataEntryFactory(0)))).asEagerSingleton(); final ShiftEDrtTaskFactoryImpl taskFactory = new ShiftEDrtTaskFactoryImpl(new EDrtTaskFactoryImpl()); bindModal(DrtTaskFactory.class).toInstance(taskFactory); @@ -81,10 +88,26 @@ drtShiftParams, new EDrtShiftStartLogic(new DefaultShiftStartLogic()), getter.get(MobsimTimer.class), taskFactory, drtShiftParams, getter.getModal(ChargingInfrastructure.class), getter.getModal(OperationFacilities.class), getter.getModal(Fleet.class)) )).asEagerSingleton(); + + // See EDrtModeOptimizerQSimModule + bindModal(VrpLegFactory.class).toProvider(modalProvider(getter -> { + DvrpConfigGroup dvrpCfg = getter.get(DvrpConfigGroup.class); + MobsimTimer timer = getter.get(MobsimTimer.class); + + // Makes basic DrtActionCreator create legs with consumption tracker + return v -> EDrtActionCreator.createLeg(dvrpCfg.mobsimMode, v, timer); + })).in(Singleton.class); + + bindModal(ShiftEDrtActionCreator.class).toProvider(modalProvider(getter -> { + VrpAgentLogic.DynActionCreator delegate = drtCfg.getPrebookingParams().isPresent() + ? getter.getModal(PrebookingActionCreator.class) + : getter.getModal(DrtActionCreator.class); - bindModal(VrpAgentLogic.DynActionCreator.class).toProvider(modalProvider( - getter -> new ShiftEDrtActionCreator(getter.getModal(PassengerHandler.class), - getter.get(MobsimTimer.class), getter.get(DvrpConfigGroup.class))) - ).asEagerSingleton(); + return new ShiftEDrtActionCreator( + new ShiftDrtActionCreator(getter.getModal(PassengerHandler.class), delegate), + getter.get(MobsimTimer.class), getter.getModal(PassengerHandler.class)); + })).asEagerSingleton(); + + bindModal(VrpAgentLogic.DynActionCreator.class).to(modalKey(ShiftEDrtActionCreator.class)); } } diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/ShiftEDrtActionCreator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/ShiftEDrtActionCreator.java index ba473f314ba..4604771bed9 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/ShiftEDrtActionCreator.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/ShiftEDrtActionCreator.java @@ -2,26 +2,17 @@ import org.matsim.contrib.drt.extension.operations.eshifts.charging.ChargingBreakActivity; import org.matsim.contrib.drt.extension.operations.eshifts.charging.ChargingChangeoverActivity; +import org.matsim.contrib.drt.extension.operations.eshifts.charging.ChargingWaitForShiftActivity; +import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftDrtActionCreator; import org.matsim.contrib.drt.schedule.DrtStopTask; -import org.matsim.contrib.drt.vrpagent.DrtActionCreator; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.passenger.PassengerHandler; -import org.matsim.contrib.dvrp.run.DvrpConfigGroup; -import org.matsim.contrib.dvrp.schedule.DriveTask; import org.matsim.contrib.dvrp.schedule.Task; -import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker; -import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTrackerImpl; -import org.matsim.contrib.dvrp.tracker.OnlineTrackerListener; -import org.matsim.contrib.dvrp.tracker.TaskTrackers; import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic.DynActionCreator; -import org.matsim.contrib.dvrp.vrpagent.VrpLeg; import org.matsim.contrib.dynagent.DynAction; import org.matsim.contrib.dynagent.DynAgent; -import org.matsim.contrib.drt.extension.operations.eshifts.charging.ChargingWaitForShiftActivity; import org.matsim.contrib.evrp.EvDvrpVehicle; import org.matsim.contrib.evrp.tracker.OfflineETaskTracker; -import org.matsim.contrib.evrp.tracker.OnlineEDriveTaskTracker; -import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftDrtActionCreator; import org.matsim.core.mobsim.framework.MobsimTimer; /** @@ -29,19 +20,12 @@ */ public class ShiftEDrtActionCreator implements DynActionCreator { - private final DynActionCreator drtActionCreator; + private final ShiftDrtActionCreator delegate; private final MobsimTimer timer; private final PassengerHandler passengerHandler; - public ShiftEDrtActionCreator(PassengerHandler passengerHandler, MobsimTimer timer, DvrpConfigGroup dvrpCfg) { - this.timer = timer; - this.passengerHandler = passengerHandler; - drtActionCreator = new ShiftDrtActionCreator(passengerHandler, new DrtActionCreator(passengerHandler, - v -> createLeg(dvrpCfg.mobsimMode, v, timer))); - } - - public ShiftEDrtActionCreator(DynActionCreator delegate, MobsimTimer timer, PassengerHandler passengerHandler) { - this.drtActionCreator = delegate; + public ShiftEDrtActionCreator(ShiftDrtActionCreator delegate, MobsimTimer timer, PassengerHandler passengerHandler) { + this.delegate = delegate; this.timer = timer; this.passengerHandler = passengerHandler; } @@ -63,21 +47,10 @@ public DynAction createAction(DynAgent dynAgent, DvrpVehicle vehicle, double now return new ChargingWaitForShiftActivity(((EDrtWaitForShiftStayTask) task).getChargingTask()); } - DynAction dynAction = drtActionCreator.createAction(dynAgent, vehicle, now); + DynAction dynAction = delegate.createAction(dynAgent, vehicle, now); if (task.getTaskTracker() == null) { task.initTaskTracker(new OfflineETaskTracker((EvDvrpVehicle) vehicle, timer)); } return dynAction; } - - private static VrpLeg createLeg(String mobsimMode, DvrpVehicle vehicle, MobsimTimer timer) { - DriveTask driveTask = (DriveTask) vehicle.getSchedule().getCurrentTask(); - VrpLeg leg = new VrpLeg(mobsimMode, driveTask.getPath()); - OnlineDriveTaskTracker onlineTracker = new OnlineDriveTaskTrackerImpl(vehicle, leg, - OnlineTrackerListener.NO_LISTENER, timer); - OnlineEDriveTaskTracker onlineETracker = new OnlineEDriveTaskTracker((EvDvrpVehicle) vehicle, timer, - onlineTracker); - TaskTrackers.initOnlineDriveTaskTracking(vehicle, leg, onlineETracker); - return leg; - } } diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/ShiftEDrtTaskFactoryImpl.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/ShiftEDrtTaskFactoryImpl.java index e7ff875f574..87490980a22 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/ShiftEDrtTaskFactoryImpl.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/ShiftEDrtTaskFactoryImpl.java @@ -1,25 +1,25 @@ package org.matsim.contrib.drt.extension.operations.eshifts.schedule; import org.matsim.api.core.v01.network.Link; +import org.matsim.contrib.drt.extension.edrt.schedule.EDrtChargingTask; +import org.matsim.contrib.drt.extension.edrt.schedule.EDrtTaskFactoryImpl; +import org.matsim.contrib.drt.extension.operations.operationFacilities.OperationFacility; +import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftBreakTask; +import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftChangeOverTask; +import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftDrtTaskFactory; +import org.matsim.contrib.drt.extension.operations.shifts.schedule.WaitForShiftStayTask; import org.matsim.contrib.drt.extension.operations.shifts.shift.DrtShift; +import org.matsim.contrib.drt.extension.operations.shifts.shift.DrtShiftBreak; 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.DrtTaskType; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.path.VrpPathWithTravelData; -import org.matsim.contrib.drt.extension.edrt.schedule.EDrtChargingTask; -import org.matsim.contrib.drt.extension.edrt.schedule.EDrtTaskFactoryImpl; import org.matsim.contrib.ev.infrastructure.Charger; import org.matsim.contrib.evrp.ChargingTask; import org.matsim.contrib.evrp.ChargingTaskImpl; import org.matsim.contrib.evrp.EvDvrpVehicle; -import org.matsim.contrib.drt.extension.operations.operationFacilities.OperationFacility; -import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftBreakTask; -import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftChangeOverTask; -import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftDrtTaskFactory; -import org.matsim.contrib.drt.extension.operations.shifts.schedule.WaitForShiftStayTask; -import org.matsim.contrib.drt.extension.operations.shifts.shift.DrtShiftBreak; /** * @author nkuehnel / MOIA diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/run/ShiftDrtModeOptimizerQSimModule.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/run/ShiftDrtModeOptimizerQSimModule.java index a765f59feda..eced5b90a1d 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/run/ShiftDrtModeOptimizerQSimModule.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/run/ShiftDrtModeOptimizerQSimModule.java @@ -40,6 +40,7 @@ import org.matsim.contrib.drt.optimizer.insertion.InsertionCostCalculator; import org.matsim.contrib.drt.optimizer.insertion.UnplannedRequestInserter; import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingStrategy; +import org.matsim.contrib.drt.prebooking.PrebookingActionCreator; import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.schedule.DrtStayTaskEndTimeCalculator; import org.matsim.contrib.drt.schedule.DrtTaskFactory; @@ -58,7 +59,9 @@ import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.dvrp.run.DvrpModes; import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater; +import org.matsim.contrib.dvrp.tracker.OnlineTrackerListener; import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic; +import org.matsim.contrib.dvrp.vrpagent.VrpLegFactory; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimTimer; import org.matsim.core.modal.ModalProviders; @@ -66,6 +69,7 @@ import org.matsim.core.router.util.TravelTime; import com.google.common.collect.ImmutableMap; +import com.google.inject.Singleton; /** * @author nkuehnel, fzwick / MOIA @@ -108,7 +112,8 @@ public DrtShifts get() { }).asEagerSingleton(); addModalComponent(DrtOptimizer.class, modalProvider( - getter -> new ShiftDrtOptimizer( + getter -> { + return new ShiftDrtOptimizer( new DefaultDrtOptimizer(drtCfg, getter.getModal(Fleet.class), getter.get(MobsimTimer.class), getter.getModal(DepotFinder.class), getter.getModal(RebalancingStrategy.class), getter.getModal(DrtScheduleInquiry.class), getter.getModal(ScheduleTimingUpdater.class), @@ -116,7 +121,8 @@ public DrtShifts get() { getter.getModal(DrtRequestInsertionRetryQueue.class) ), getter.getModal(DrtShiftDispatcher.class), - getter.getModal(ScheduleTimingUpdater.class)))); + getter.getModal(ScheduleTimingUpdater.class)); + })); bindModal(DrtShiftDispatcher.class).toProvider(modalProvider( getter -> new DrtShiftDispatcherImpl(getter.getModal(DrtShifts.class), getter.getModal(Fleet.class), @@ -129,7 +135,7 @@ shiftsParams, new DefaultShiftStartLogic(), new DefaultAssignShiftToVehicleLogic getter -> new ShiftInsertionCostCalculator(getter.get(MobsimTimer.class), new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class))))); - bindModal(VehicleEntry.EntryFactory.class).toInstance(new ShiftVehicleDataEntryFactory(new VehicleDataEntryFactoryImpl(drtCfg))); + bindModal(VehicleEntry.EntryFactory.class).toInstance(new ShiftVehicleDataEntryFactory(new VehicleDataEntryFactoryImpl())); final ShiftDrtTaskFactoryImpl taskFactory = new ShiftDrtTaskFactoryImpl(new DrtTaskFactoryImpl()); bindModal(DrtTaskFactory.class).toInstance(taskFactory); @@ -156,13 +162,26 @@ shiftsParams, new DefaultShiftStartLogic(), new DefaultAssignShiftToVehicleLogic new ShiftDrtStayTaskEndTimeCalculator(shiftsParams, new DrtStayTaskEndTimeCalculator(getter.getModal(StopTimeCalculator.class))))) ).asEagerSingleton(); - - bindModal(VrpAgentLogic.DynActionCreator.class).toProvider(modalProvider( - (getter) -> new ShiftDrtActionCreator(getter.getModal(PassengerHandler.class), - new DrtActionCreator(getter.getModal(PassengerHandler.class), getter.get(MobsimTimer.class), - getter.get(DvrpConfigGroup.class)))) - ).asEagerSingleton(); - + + // see DrtModeOptimizerQSimModule + bindModal(VrpLegFactory.class).toProvider(modalProvider(getter -> { + DvrpConfigGroup dvrpCfg = getter.get(DvrpConfigGroup.class); + MobsimTimer timer = getter.get(MobsimTimer.class); + + return v -> VrpLegFactory.createWithOnlineTracker(dvrpCfg.mobsimMode, v, OnlineTrackerListener.NO_LISTENER, + timer); + })).in(Singleton.class); + + bindModal(ShiftDrtActionCreator.class).toProvider(modalProvider((getter) -> { + VrpAgentLogic.DynActionCreator delegate = drtCfg.getPrebookingParams().isPresent() + ? getter.getModal(PrebookingActionCreator.class) + : getter.getModal(DrtActionCreator.class); + + // adds shift tasks + return new ShiftDrtActionCreator(getter.getModal(PassengerHandler.class), delegate); + })).asEagerSingleton(); + + bindModal(VrpAgentLogic.DynActionCreator.class).to(modalKey(ShiftDrtActionCreator.class)); bindModal(Fleet.class).toProvider(new ModalProviders.AbstractProvider<>(getMode(), DvrpModes::mode) { @Override diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/preplanned/optimizer/PreplannedDrtModeOptimizerQSimModule.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/preplanned/optimizer/PreplannedDrtModeOptimizerQSimModule.java index 8952fed6b09..d5b560d31fc 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/preplanned/optimizer/PreplannedDrtModeOptimizerQSimModule.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/preplanned/optimizer/PreplannedDrtModeOptimizerQSimModule.java @@ -34,12 +34,17 @@ import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater; +import org.matsim.contrib.dvrp.tracker.OnlineTrackerListener; import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic; +import org.matsim.contrib.dvrp.vrpagent.VrpLegFactory; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimTimer; import org.matsim.core.router.costcalculators.TravelDisutilityFactory; import org.matsim.core.router.util.TravelTime; +import com.google.common.base.Preconditions; +import com.google.inject.Singleton; + /** * @author Michal Maciejewski (michalm) */ @@ -61,10 +66,17 @@ protected void configureQSim() { getter.getModal(ScheduleTimingUpdater.class)))); bindModal(DrtTaskFactory.class).toInstance(new DrtTaskFactoryImpl()); + + bindModal(VrpLegFactory.class).toProvider(modalProvider(getter -> { + DvrpConfigGroup dvrpCfg = getter.get(DvrpConfigGroup.class); + MobsimTimer timer = getter.get(MobsimTimer.class); + + return v -> VrpLegFactory.createWithOnlineTracker(dvrpCfg.mobsimMode, v, OnlineTrackerListener.NO_LISTENER, + timer); + })).in(Singleton.class); - bindModal(VrpAgentLogic.DynActionCreator.class).toProvider(modalProvider( - getter -> new DrtActionCreator(getter.getModal(PassengerHandler.class), getter.get(MobsimTimer.class), - getter.get(DvrpConfigGroup.class)))).asEagerSingleton(); + Preconditions.checkState(drtCfg.getPrebookingParams().isEmpty(), "cannot use preplanned schedules with prebooking"); + bindModal(VrpAgentLogic.DynActionCreator.class).to(modalKey(DrtActionCreator.class)); bindModal(VrpOptimizer.class).to(modalKey(DrtOptimizer.class)); diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java index 49940884fc4..1fb77797343 100644 --- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java +++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java @@ -21,8 +21,18 @@ import java.net.URL; import org.junit.Test; +import org.matsim.contrib.drt.prebooking.PrebookingParams; +import org.matsim.contrib.drt.prebooking.logic.ProbabilityBasedPrebookingLogic; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; +import org.matsim.contrib.dvrp.run.DvrpConfigGroup; +import org.matsim.contrib.ev.EvConfigGroup; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.Controler; import org.matsim.core.utils.io.IOUtils; import org.matsim.examples.ExamplesUtils; +import org.matsim.vis.otfvis.OTFVisConfigGroup; /** * @author michalm @@ -33,4 +43,20 @@ public void test() { URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_edrt_config.xml"); RunEDrtScenario.run(configUrl, false); } + + @Test + public void testWithPrebooking() { + URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_edrt_config.xml"); + + Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(), + new OTFVisConfigGroup(), new EvConfigGroup()); + + DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(config); + drtConfig.addParameterSet(new PrebookingParams()); + + Controler controller = RunEDrtScenario.createControler(config, false); + ProbabilityBasedPrebookingLogic.install(controller, drtConfig, 0.5, 4.0 * 3600.0); + + controller.run(); + } } diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/fiss/RunFissDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/fiss/RunFissDrtScenarioIT.java index 2f88290727e..c5ea83cbce9 100644 --- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/fiss/RunFissDrtScenarioIT.java +++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/fiss/RunFissDrtScenarioIT.java @@ -173,7 +173,7 @@ public void install() { } run.run(); - Assert.assertEquals(23961, linkCounter.getLinkLeaveCount()); + Assert.assertEquals(23817, linkCounter.getLinkLeaveCount()); } static class LinkCounter implements LinkLeaveEventHandler { 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 ab98bfcc71a..62f9a26f467 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 @@ -121,9 +121,9 @@ public class DrtAnalysisControlerListener implements IterationEndsListener, Shut this.delimiter = config.global().getDefaultDelimiter(); } - private record DrtLeg(Id request, double departureTime, Id person, Id vehicle, Id fromLinkId, Coord fromCoord, + private record DrtLeg(Id request, double submissionTime, double departureTime, Id person, Id vehicle, Id fromLinkId, Coord fromCoord, Id toLinkId, Coord toCoord, double waitTime, double unsharedDistanceEstimate_m, double unsharedTimeEstimate_m, - double arrivalTime, double fare, double latestDepartureTime, double latestArrivalTime) { + double arrivalTime, double fare, double earliestDepartureTime, double latestDepartureTime, double latestArrivalTime) { } private static DrtLeg newDrtLeg(EventSequence sequence, Function, ? extends Link> linkProvider) { @@ -132,6 +132,7 @@ private static DrtLeg newDrtLeg(EventSequence sequence, Function, ? ext PersonDepartureEvent departureEvent = sequence.getDeparture().get(); PassengerPickedUpEvent pickedUpEvent = sequence.getPickedUp().get(); var request = submittedEvent.getRequestId(); + var submissionTime = submittedEvent.getTime(); var departureTime = departureEvent.getTime(); var person = submittedEvent.getPersonId(); var vehicle = pickedUpEvent.getVehicleId(); @@ -145,10 +146,11 @@ private static DrtLeg newDrtLeg(EventSequence sequence, Function, ? ext var arrivalTime = sequence.getDroppedOff().get().getTime(); // PersonMoneyEvent has negative amount because the agent's money is reduced -> for the operator that is a positive amount var fare = sequence.getDrtFares().stream().mapToDouble(PersonMoneyEvent::getAmount).sum(); + var earliestDepartureTime = sequence.getSubmitted().getEarliestDepartureTime(); var latestDepartureTime = sequence.getSubmitted().getLatestPickupTime(); var latestArrivalTime = sequence.getSubmitted().getLatestDropoffTime(); - return new DrtLeg(request, departureTime, person, vehicle, fromLinkId, fromCoord, toLinkId, toCoord, waitTime, unsharedDistanceEstimate_m, - unsharedTimeEstimate_m, arrivalTime, fare, latestDepartureTime, latestArrivalTime); + return new DrtLeg(request, submissionTime, departureTime, person, vehicle, fromLinkId, fromCoord, toLinkId, toCoord, waitTime, unsharedDistanceEstimate_m, + unsharedTimeEstimate_m, arrivalTime, fare, earliestDepartureTime, latestDepartureTime, latestArrivalTime); } @Override @@ -174,12 +176,13 @@ public void notifyIterationEnds(IterationEndsEvent event) { .collect(toList()); collection2Text(drtEventSequenceCollector.getRejectedRequestSequences().values(), filename(event, "drt_rejections", ".csv"), - String.join(delimiter, "time", "personId", "fromLinkId", "toLinkId", "fromX", "fromY", "toX", "toY"), seq -> { + String.join(delimiter, "time", "personId", "requestId", "fromLinkId", "toLinkId", "fromX", "fromY", "toX", "toY"), seq -> { DrtRequestSubmittedEvent submission = seq.getSubmitted(); Coord fromCoord = network.getLinks().get(submission.getFromLinkId()).getToNode().getCoord(); Coord toCoord = network.getLinks().get(submission.getToLinkId()).getToNode().getCoord(); return String.join(delimiter, submission.getTime() + "",// submission.getPersonId() + "",// + submission.getRequestId() + "",// submission.getFromLinkId() + "",// submission.getToLinkId() + "",// fromCoord.getX() + "",// @@ -209,8 +212,11 @@ public void notifyIterationEnds(IterationEndsEvent event) { String occStats = summarizeDetailedOccupancyStats(drtVehicleStats.getVehicleStates(), delimiter, maxcap); writeIterationVehicleStats(vehStats, occStats, event.getIteration()); if (drtCfg.plotDetailedCustomerStats) { - String header = String.join(delimiter, "departureTime",// + String header = String.join(delimiter, // + "submissionTime", // + "departureTime",// "personId",// + "requestId",// "vehicleId",// "fromLinkId",// "fromX",// @@ -224,12 +230,15 @@ public void notifyIterationEnds(IterationEndsEvent event) { "travelDistance_m",// "directTravelDistance_m",// "fareForLeg", // + "earliestDepartureTime", "latestDepartureTime", // "latestArrivalTime"); collection2Text(legs, filename(event, "drt_legs", ".csv"), header, leg -> String.join(delimiter,// + (Double)leg.submissionTime + "",// (Double)leg.departureTime + "",// leg.person + "",// + leg.request + "",// leg.vehicle + "",// leg.fromLinkId + "",// format.format(leg.fromCoord.getX()),// @@ -243,6 +252,7 @@ public void notifyIterationEnds(IterationEndsEvent event) { format.format(drtVehicleStats.getTravelDistances().get(leg.request)),// format.format(leg.unsharedDistanceEstimate_m),// format.format(leg.fare), // + format.format(leg.earliestDepartureTime), // format.format(leg.latestDepartureTime), // format.format(leg.latestArrivalTime))); } @@ -365,7 +375,7 @@ private void writeAndPlotWaitTimeEstimateComparison(Collection pe for (EventSequence seq : performedRequestEventSequences) { if (seq.getPickedUp().isPresent()) { double actualWaitTime = seq.getPickedUp().get().getTime() - seq.getDeparture().get().getTime(); - double estimatedWaitTime = seq.getScheduled().get().getPickupTime() - seq.getDeparture().get().getTime(); + double estimatedWaitTime = seq.getScheduled().get().getPickupTime() - seq.getSubmitted().getEarliestDepartureTime(); bw.append(line(seq.getSubmitted().getRequestId(), actualWaitTime, estimatedWaitTime, actualWaitTime - estimatedWaitTime)); times.add(actualWaitTime, estimatedWaitTime); } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtEventSequenceCollector.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtEventSequenceCollector.java index f70931a4e06..04a89f30c36 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtEventSequenceCollector.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtEventSequenceCollector.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -52,19 +51,37 @@ 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.PassengerWaitingEvent; +import org.matsim.contrib.dvrp.passenger.PassengerWaitingEventHandler; import com.google.common.base.Preconditions; +import com.google.common.base.Verify; /** - * Creates PerformedRequestEventSequence (for scheduled requests) and RejectedRequestEventSequence (for rejected requests). - * Almost all data for request/leg analysis is there (except info on actual paths), so should be quite reusable. + * Creates PerformedRequestEventSequence (for scheduled requests) and + * RejectedRequestEventSequence (for rejected requests). Almost all data for + * request/leg analysis is there (except info on actual paths), so should be + * quite reusable. + * + * Without prebooking, the order of sequences is always the same: First the + * agent *departs* then the request is *submitted*, then it is *rejected* or + * picked up. With prebooking, the order of departure and submission can be + * reversed: An agent first submits the request, and only later departs on the + * leg. Since *departure* is core MATSim, the respective event has no + * information about the request identifier. It could now happen that two + * submission with same characteristics have been submitted (person id, origin + * id). Then it is not clear which request belongs to the current departure. For + * that purpose the PassengerWaiting event has been introduced which is fired + * right after a departure has been processed by a DRT-related PassengerEngine. + * This events allows to link the latest departure of an agent to a request id. * * @author jbischoff * @author Michal Maciejewski + * @author Sebastian Hörl (sebhoerl), IRT SystemX */ public class DrtEventSequenceCollector implements PassengerRequestRejectedEventHandler, PassengerRequestScheduledEventHandler, - DrtRequestSubmittedEventHandler, PassengerPickedUpEventHandler, PassengerDroppedOffEventHandler, + DrtRequestSubmittedEventHandler, PassengerWaitingEventHandler, PassengerPickedUpEventHandler, PassengerDroppedOffEventHandler, PersonMoneyEventHandler, PersonDepartureEventHandler { public static class EventSequence { @@ -137,8 +154,8 @@ public boolean isCompleted() { private final Map, EventSequence> sequences = new HashMap<>(); private final List drtFarePersonMoneyEvents = new ArrayList<>(); - private final Map, List> sequencesWithoutDeparture = new HashMap<>(); - private final Map, List> departuresWithoutSequence = new HashMap<>(); + private final Map, PersonDepartureEvent> latestDepartures = new HashMap<>(); + private final Map, PersonDepartureEvent> waitingForSubmission = new HashMap<>(); public DrtEventSequenceCollector(String mode) { this.mode = mode; @@ -158,6 +175,8 @@ public Map, EventSequence> getRejectedRequestSequences() { public Map, EventSequence> getPerformedRequestSequences() { return sequences.entrySet().stream() // .filter(e -> e.getValue().getRejected().isEmpty()) // + .filter(e -> e.getValue().getDeparture().isPresent()) // + .filter(e -> e.getValue().getPickedUp().isPresent()) // .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); } @@ -169,8 +188,8 @@ public List getDrtFarePersonMoneyEvents() { public void reset(int iteration) { sequences.clear(); drtFarePersonMoneyEvents.clear(); - sequencesWithoutDeparture.clear(); - departuresWithoutSequence.clear(); + latestDepartures.clear(); + waitingForSubmission.clear(); } @Override @@ -178,35 +197,42 @@ public void handleEvent(DrtRequestSubmittedEvent event) { if (event.getMode().equals(mode)) { EventSequence sequence = new EventSequence(event); sequences.put(event.getRequestId(), sequence); - - PersonDepartureEvent departureEvent = popDepartureForSubmission(event); - - if (departureEvent == null) { - // We have a submitted request, but no departure event yet (i.e. a prebooking). We start the - // sequence and note down the person id to fill in the departure event later on. - sequencesWithoutDeparture.computeIfAbsent(event.getPersonId(), id -> new LinkedList<>()).add(sequence); - } else { - sequence.departure = departureEvent; - } + + // if we already have a departure + sequence.departure = waitingForSubmission.remove(event.getRequestId()); } } @Override public void handleEvent(PersonDepartureEvent event) { if (event.getLegMode().equals(mode)) { - EventSequence sequence = popSequenceForDeparture(event); + // note down the departure event here, for now we don't know which request it + // belongs to, see below + Preconditions.checkState(!latestDepartures.containsKey(event.getPersonId()), + "Attempt to register a departure event for " + mode + " and person " + event.getPersonId() + + ", but there is still a departure that has not been consumed"); + latestDepartures.put(event.getPersonId(), event); + } + } + + @Override + public void handleEvent(PassengerWaitingEvent event) { + if (event.getMode().equals(mode)) { + // must exist, otherwise something is wrong + PersonDepartureEvent departureEvent = Objects.requireNonNull(latestDepartures.remove(event.getPersonId())); - if (sequence == null) { - // We have a departure event, but no submission yet (i.e. and instant booking). - // We note down the departure event here to recover it later when the submission - // is down (usually right after). - departuresWithoutSequence.computeIfAbsent(event.getPersonId(), id -> new LinkedList<>()).add(event); + EventSequence sequence = sequences.get(event.getRequestId()); + if (sequence != null) { + // prebooked request, we already have the submission + Verify.verifyNotNull(sequence.submitted); + sequence.departure = departureEvent; } else { - sequence.departure = event; + // immediate request, submission event should come soon + waitingForSubmission.put(event.getRequestId(), departureEvent); } } } - + @Override public void handleEvent(PassengerRequestScheduledEvent event) { if (event.getMode().equals(mode)) { @@ -254,72 +280,4 @@ public void handleEvent(PersonMoneyEvent event) { } } } - - /* - * This function is helper that finds a PersonDepartureEvent for a given - * DrtRequestSubmittedEvent. This means that the departure has happened before - * submission, which is the usually case for instant requests. - */ - private PersonDepartureEvent popDepartureForSubmission(DrtRequestSubmittedEvent event) { - List potentialDepartures = departuresWithoutSequence.get(event.getPersonId()); - PersonDepartureEvent result = null; - - if (potentialDepartures != null) { - Iterator iterator = potentialDepartures.iterator(); - - while (iterator.hasNext()) { - PersonDepartureEvent departureEvent = iterator.next(); - - if (event.getFromLinkId().equals(departureEvent.getLinkId())) { - if (result != null) { - throw new IllegalStateException( - "Ambiguous matching between submission and departure - not sure how to solve this"); - } - - iterator.remove(); - result = departureEvent; - } - } - - if (potentialDepartures.size() == 0) { - departuresWithoutSequence.remove(event.getPersonId()); - } - } - - return result; - } - - /* - * This function is helper that finds a sequence given a PersonDepartureEvent. - * This means that a sequence has started (the request has been submitted) - * before the agent has departed, i.e. this is a pre-booking of some sort. - */ - private EventSequence popSequenceForDeparture(PersonDepartureEvent event) { - EventSequence result = null; - List potentialSequences = sequencesWithoutDeparture.get(event.getPersonId()); - - if (potentialSequences != null) { - Iterator iterator = potentialSequences.iterator(); - - while (iterator.hasNext()) { - EventSequence sequence = iterator.next(); - - if (sequence.submitted.getFromLinkId().equals(event.getLinkId())) { - if (result != null) { - throw new IllegalStateException( - "Ambiguous matching between submission and departure - not sure how to solve this"); - } - - iterator.remove(); - result = sequence; - } - } - - if (potentialSequences.size() == 0) { - sequencesWithoutDeparture.remove(event.getPersonId()); - } - } - - return result; - } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java index 5eef0f27f20..436601c82d2 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java @@ -19,7 +19,11 @@ package org.matsim.contrib.drt.optimizer; +import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STAY; + +import java.util.LinkedList; import java.util.List; +import java.util.Queue; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; @@ -37,13 +41,10 @@ import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.fleet.Fleet; import org.matsim.contrib.dvrp.optimizer.Request; -import org.matsim.contrib.dvrp.passenger.RequestQueue; import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater; import org.matsim.core.mobsim.framework.MobsimTimer; import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent; -import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STAY; - /** * @author michalm */ @@ -62,7 +63,7 @@ public class DefaultDrtOptimizer implements DrtOptimizer { private final UnplannedRequestInserter requestInserter; private final DrtRequestInsertionRetryQueue insertionRetryQueue; - private final RequestQueue unplannedRequests; + private final Queue unplannedRequests = new LinkedList<>(); public DefaultDrtOptimizer(DrtConfigGroup drtCfg, Fleet fleet, MobsimTimer mobsimTimer, DepotFinder depotFinder, RebalancingStrategy rebalancingStrategy, DrtScheduleInquiry scheduleInquiry, ScheduleTimingUpdater scheduleTimingUpdater, @@ -79,21 +80,18 @@ public DefaultDrtOptimizer(DrtConfigGroup drtCfg, Fleet fleet, MobsimTimer mobsi this.insertionRetryQueue = insertionRetryQueue; rebalancingInterval = drtCfg.getRebalancingParams().map(rebalancingParams -> rebalancingParams.interval).orElse(null); - unplannedRequests = RequestQueue.withLimitedAdvanceRequestPlanningHorizon(drtCfg.advanceRequestPlanningHorizon); } @Override public void notifyMobsimBeforeSimStep(@SuppressWarnings("rawtypes") MobsimBeforeSimStepEvent e) { - unplannedRequests.updateQueuesOnNextTimeSteps(e.getSimulationTime()); - boolean scheduleTimingUpdated = false; - if (!unplannedRequests.getSchedulableRequests().isEmpty() || insertionRetryQueue.hasRequestsToRetryNow(e.getSimulationTime())) { + if (!unplannedRequests.isEmpty() || insertionRetryQueue.hasRequestsToRetryNow(e.getSimulationTime())) { for (DvrpVehicle v : fleet.getVehicles().values()) { scheduleTimingUpdater.updateTimings(v); } scheduleTimingUpdated = true; - requestInserter.scheduleUnplannedRequests(unplannedRequests.getSchedulableRequests()); + requestInserter.scheduleUnplannedRequests(unplannedRequests); } relocateVehiclesToDepot(drtCfg.returnToDepotEvaluationInterval, drtCfg.returnToDepotTimeout); @@ -126,7 +124,7 @@ private void rebalanceFleet() { @Override public void requestSubmitted(Request request) { - unplannedRequests.addRequest((DrtRequest)request); + unplannedRequests.add((DrtRequest)request); } @Override diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtModeOptimizerQSimModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtModeOptimizerQSimModule.java index a9bc339564f..8534468ce81 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtModeOptimizerQSimModule.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtModeOptimizerQSimModule.java @@ -37,6 +37,7 @@ import org.matsim.contrib.drt.optimizer.insertion.selective.SelectiveInsertionSearchQSimModule; import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingStrategy; import org.matsim.contrib.drt.passenger.DrtOfferAcceptor; +import org.matsim.contrib.drt.prebooking.PrebookingActionCreator; import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.schedule.DrtStayTaskEndTimeCalculator; import org.matsim.contrib.drt.schedule.DrtTaskFactory; @@ -50,12 +51,13 @@ import org.matsim.contrib.drt.vrpagent.DrtActionCreator; import org.matsim.contrib.dvrp.fleet.Fleet; import org.matsim.contrib.dvrp.optimizer.VrpOptimizer; -import org.matsim.contrib.dvrp.passenger.PassengerHandler; import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.dvrp.run.DvrpModes; import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater; +import org.matsim.contrib.dvrp.tracker.OnlineTrackerListener; import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic; +import org.matsim.contrib.dvrp.vrpagent.VrpLegFactory; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimTimer; import org.matsim.core.modal.ModalProviders; @@ -64,6 +66,7 @@ import org.matsim.core.router.util.TravelTime; import com.google.inject.Inject; +import com.google.inject.Singleton; /** * @author Michal Maciejewski (michalm) @@ -79,11 +82,13 @@ public DrtModeOptimizerQSimModule(DrtConfigGroup drtCfg) { @Override protected void configureQSim() { addModalComponent(DrtOptimizer.class, modalProvider( - getter -> new DefaultDrtOptimizer(drtCfg, getter.getModal(Fleet.class), getter.get(MobsimTimer.class), + getter -> { + return new DefaultDrtOptimizer(drtCfg, getter.getModal(Fleet.class), getter.get(MobsimTimer.class), getter.getModal(DepotFinder.class), getter.getModal(RebalancingStrategy.class), getter.getModal(DrtScheduleInquiry.class), getter.getModal(ScheduleTimingUpdater.class), getter.getModal(EmptyVehicleRelocator.class), getter.getModal(UnplannedRequestInserter.class), - getter.getModal(DrtRequestInsertionRetryQueue.class)))); + getter.getModal(DrtRequestInsertionRetryQueue.class)); + })); bindModal(DepotFinder.class).toProvider( modalProvider(getter -> new NearestStartLinkAsDepot(getter.getModal(Fleet.class)))).asEagerSingleton(); @@ -100,14 +105,15 @@ protected void configureQSim() { getter.getModal(RequestInsertionScheduler.class), getter.getModal(VehicleEntry.EntryFactory.class), getter.getModal(DrtInsertionSearch.class), getter.getModal(DrtRequestInsertionRetryQueue.class), getter.getModal(DrtOfferAcceptor.class), - getter.getModal(QSimScopeForkJoinPoolHolder.class).getPool()))).asEagerSingleton(); + getter.getModal(QSimScopeForkJoinPoolHolder.class).getPool(), + getter.getModal(PassengerStopDurationProvider.class)))).asEagerSingleton(); bindModal(InsertionCostCalculator.class).toProvider(modalProvider( getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class)))); install(getInsertionSearchQSimModule(drtCfg)); - bindModal(VehicleEntry.EntryFactory.class).toInstance(new VehicleDataEntryFactoryImpl(drtCfg)); + bindModal(VehicleEntry.EntryFactory.class).toInstance(new VehicleDataEntryFactoryImpl()); bindModal(CostCalculationStrategy.class).to(drtCfg.rejectRequestIfMaxWaitOrTravelTimeViolated ? CostCalculationStrategy.RejectSoftConstraintViolations.class : @@ -132,12 +138,13 @@ public EmptyVehicleRelocator get() { }).asEagerSingleton(); bindModal(DrtScheduleInquiry.class).to(DrtScheduleInquiry.class).asEagerSingleton(); - + + boolean scheduleWaitBeforeDrive = drtCfg.getPrebookingParams().map(p -> p.scheduleWaitBeforeDrive).orElse(false); bindModal(RequestInsertionScheduler.class).toProvider(modalProvider( getter -> new DefaultRequestInsertionScheduler(getter.getModal(Fleet.class), getter.get(MobsimTimer.class), getter.getModal(TravelTime.class), getter.getModal(ScheduleTimingUpdater.class), getter.getModal(DrtTaskFactory.class), - getter.getModal(StopTimeCalculator.class)))) + getter.getModal(StopTimeCalculator.class), scheduleWaitBeforeDrive))) .asEagerSingleton(); bindModal(DrtOfferAcceptor.class).toInstance(DrtOfferAcceptor.DEFAULT_ACCEPTOR); @@ -146,9 +153,19 @@ public EmptyVehicleRelocator get() { getter -> new ScheduleTimingUpdater(getter.get(MobsimTimer.class), new DrtStayTaskEndTimeCalculator(getter.getModal(StopTimeCalculator.class))))).asEagerSingleton(); - bindModal(VrpAgentLogic.DynActionCreator.class).toProvider(modalProvider( - getter -> new DrtActionCreator(getter.getModal(PassengerHandler.class), getter.get(MobsimTimer.class), - getter.get(DvrpConfigGroup.class)))).asEagerSingleton(); + bindModal(VrpLegFactory.class).toProvider(modalProvider(getter -> { + DvrpConfigGroup dvrpCfg = getter.get(DvrpConfigGroup.class); + MobsimTimer timer = getter.get(MobsimTimer.class); + + return v -> VrpLegFactory.createWithOnlineTracker(dvrpCfg.mobsimMode, v, OnlineTrackerListener.NO_LISTENER, + timer); + })).in(Singleton.class); + + if (drtCfg.getPrebookingParams().isEmpty()) { + bindModal(VrpAgentLogic.DynActionCreator.class).to(modalKey(DrtActionCreator.class)); + } else { + bindModal(VrpAgentLogic.DynActionCreator.class).to(modalKey(PrebookingActionCreator.class)); + } bindModal(VrpOptimizer.class).to(modalKey(DrtOptimizer.class)); } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImpl.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImpl.java index 98b961e2204..663b480ff74 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImpl.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImpl.java @@ -18,14 +18,13 @@ package org.matsim.contrib.drt.optimizer; +import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STAY; import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STOP; import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.getBaseTypeOrElseThrow; import java.util.ArrayList; import java.util.List; -import org.matsim.contrib.drt.passenger.AcceptedDrtRequest; -import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.schedule.DrtStopTask; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.schedule.DriveTask; @@ -37,31 +36,13 @@ import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker; import org.matsim.contrib.dvrp.util.LinkTimePair; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; /** * @author michalm */ public class VehicleDataEntryFactoryImpl implements VehicleEntry.EntryFactory { - private final double lookAhead; - - public VehicleDataEntryFactoryImpl(DrtConfigGroup drtCfg) { - if (drtCfg.rejectRequestIfMaxWaitOrTravelTimeViolated) { - lookAhead = drtCfg.maxWaitTime - drtCfg.stopDuration; - Preconditions.checkArgument(lookAhead >= 0, - "maxWaitTime must not be smaller than stopDuration"); - } else { - // if no rejection due to max wait time, the look ahead is infinite - lookAhead = Double.POSITIVE_INFINITY; - } - } - public VehicleEntry create(DvrpVehicle vehicle, double currentTime) { - if (isNotEligibleForRequestInsertion(vehicle, currentTime)) { - return null; - } - Schedule schedule = vehicle.getSchedule(); final LinkTimePair start; final Task startTask; @@ -89,9 +70,21 @@ public VehicleEntry create(DvrpVehicle vehicle, double currentTime) { List tasks = schedule.getTasks(); List stopTasks = new ArrayList<>(); + + // find stop tasks and note down stay time before each task + double accumulatedStayTime = 0.0; + if (startTask != null && STAY.isBaseTypeOf(startTask)) { + accumulatedStayTime = Math.max(0.0, startTask.getEndTime() - currentTime); + } + + List precedingStayTimes = new ArrayList<>(); for (Task task : tasks.subList(nextTaskIdx, tasks.size())) { - if (STOP.isBaseTypeOf(task)) { + if (STAY.isBaseTypeOf(task)) { + accumulatedStayTime += task.getEndTime() - task.getBeginTime(); + } else if (STOP.isBaseTypeOf(task)) { stopTasks.add((DrtStopTask)task); + precedingStayTimes.add(accumulatedStayTime); + accumulatedStayTime = 0.0; } } @@ -106,17 +99,13 @@ public VehicleEntry create(DvrpVehicle vehicle, double currentTime) { ? new Waypoint.Stop((DrtStopTask) startTask, 0) : null; - var slackTimes = computeSlackTimes(vehicle, currentTime, stops, startStop); + var slackTimes = computeSlackTimes(vehicle, currentTime, stops, startStop, precedingStayTimes); return new VehicleEntry(vehicle, new Waypoint.Start(startTask, start.link, start.time, outgoingOccupancy), - ImmutableList.copyOf(stops), slackTimes, currentTime); - } - - public boolean isNotEligibleForRequestInsertion(DvrpVehicle vehicle, double currentTime) { - return currentTime + lookAhead < vehicle.getServiceBeginTime() || currentTime >= vehicle.getServiceEndTime(); + ImmutableList.copyOf(stops), slackTimes, precedingStayTimes, currentTime); } - static double[] computeSlackTimes(DvrpVehicle vehicle, double now, Waypoint.Stop[] stops, Waypoint.Stop start) { + static double[] computeSlackTimes(DvrpVehicle vehicle, double now, Waypoint.Stop[] stops, Waypoint.Stop start, List precedingStayTimes) { double[] slackTimes = new double[stops.length + 2]; //vehicle @@ -128,6 +117,7 @@ static double[] computeSlackTimes(DvrpVehicle vehicle, double now, Waypoint.Stop var stop = stops[i]; slackTime = Math.min(stop.latestArrivalTime - stop.task.getBeginTime(), slackTime); slackTime = Math.min(stop.latestDepartureTime - stop.task.getEndTime(), slackTime); + slackTime += precedingStayTimes.get(i); // reset slack before prebooked request slackTimes[i + 1] = slackTime; } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleEntry.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleEntry.java index df2bd47957b..60f2d0f9766 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleEntry.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleEntry.java @@ -20,6 +20,8 @@ package org.matsim.contrib.drt.optimizer; +import java.util.List; + import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import com.google.common.collect.ImmutableList; @@ -36,16 +38,18 @@ public interface EntryFactory { public final Waypoint.Start start; public final ImmutableList stops; public final Waypoint.End end; - private final double[] slackTimes;// for all insertion points + private final double[] slackTimes;// for all insertion points (start, stops, end) + private final List precedingStayTimes;// for all stops public final double createTime; public VehicleEntry(DvrpVehicle vehicle, Waypoint.Start start, ImmutableList stops, - double[] slackTimes, double createTime) { + double[] slackTimes, List precedingStayTimes, double createTime) { this.vehicle = vehicle; this.start = start; this.stops = stops; this.end = Waypoint.End.OPEN_END; this.slackTimes = slackTimes; + this.precedingStayTimes = precedingStayTimes; this.createTime = createTime; } @@ -55,6 +59,7 @@ protected VehicleEntry(VehicleEntry that) { this.stops = that.stops; this.end = that.end; this.slackTimes = that.slackTimes; + this.precedingStayTimes = that.precedingStayTimes; this.createTime = that.createTime; } @@ -73,4 +78,8 @@ public double getSlackTime(int index) { public double getStartSlackTime() { return slackTimes[0]; } + + public double getPrecedingStayTime(int index) { + return precedingStayTimes.get(index); + } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/depot/Depots.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/depot/Depots.java index 50919dde3af..3b2152eec90 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/depot/Depots.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/depot/Depots.java @@ -50,6 +50,13 @@ public static boolean isSwitchingFromStopToStay(DvrpVehicle vehicle) { if (!STAY.isBaseTypeOf(currentTask)) { return false; } + + // only if stay task is last task: with prebooking we may also idle during the day, but + // currently all the downstream relocation/charging logic assumes that we only stay at + // the end of the schedule + if (currentTask.getTaskIdx() < schedule.getTaskCount() - 1) { + return false; + } // previous task was STOP int previousTaskIdx = currentTask.getTaskIdx() - 1; diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultInsertionCostCalculator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultInsertionCostCalculator.java index 849d2dc02c4..1b16c140d50 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultInsertionCostCalculator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultInsertionCostCalculator.java @@ -50,8 +50,14 @@ public DefaultInsertionCostCalculator(CostCalculationStrategy costCalculationStr public double calculate(DrtRequest drtRequest, Insertion insertion, DetourTimeInfo detourTimeInfo) { var vEntry = insertion.vehicleEntry; + // in case of prebooking, we may have intermediate stay times after pickup + // insertion that may reduce the effective pickup delay that remains that the + // dropoff insertion point + double effectiveDropoffTimeLoss = InsertionDetourTimeCalculator.calculateRemainingPickupTimeLossAtDropoff( + insertion, detourTimeInfo.pickupDetourInfo) + detourTimeInfo.dropoffDetourInfo.dropoffTimeLoss; + if (vEntry.getSlackTime(insertion.pickup.index) < detourTimeInfo.pickupDetourInfo.pickupTimeLoss - || vEntry.getSlackTime(insertion.dropoff.index) < detourTimeInfo.getTotalTimeLoss()) { + || vEntry.getSlackTime(insertion.dropoff.index) < effectiveDropoffTimeLoss) { return INFEASIBLE_SOLUTION_COST; } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserter.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserter.java index 056d5ae0d4e..34503207f82 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserter.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserter.java @@ -38,6 +38,7 @@ import org.matsim.contrib.drt.passenger.DrtRequest; import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.scheduler.RequestInsertionScheduler; +import org.matsim.contrib.drt.stops.PassengerStopDurationProvider; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.fleet.Fleet; import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent; @@ -63,22 +64,23 @@ public class DefaultUnplannedRequestInserter implements UnplannedRequestInserter private final DrtInsertionSearch insertionSearch; private final DrtRequestInsertionRetryQueue insertionRetryQueue; private final DrtOfferAcceptor drtOfferAcceptor; - private final ForkJoinPool forkJoinPool; + private final ForkJoinPool forkJoinPool; + private final PassengerStopDurationProvider stopDurationProvider; public DefaultUnplannedRequestInserter(DrtConfigGroup drtCfg, Fleet fleet, MobsimTimer mobsimTimer, EventsManager eventsManager, RequestInsertionScheduler insertionScheduler, VehicleEntry.EntryFactory vehicleEntryFactory, DrtInsertionSearch insertionSearch, DrtRequestInsertionRetryQueue insertionRetryQueue, DrtOfferAcceptor drtOfferAcceptor, - ForkJoinPool forkJoinPool) { + ForkJoinPool forkJoinPool, PassengerStopDurationProvider stopDurationProvider) { this(drtCfg.getMode(), fleet, mobsimTimer::getTimeOfDay, eventsManager, insertionScheduler, vehicleEntryFactory, - insertionRetryQueue, insertionSearch, drtOfferAcceptor, forkJoinPool); + insertionRetryQueue, insertionSearch, drtOfferAcceptor, forkJoinPool, stopDurationProvider); } @VisibleForTesting DefaultUnplannedRequestInserter(String mode, Fleet fleet, DoubleSupplier timeOfDay, EventsManager eventsManager, RequestInsertionScheduler insertionScheduler, VehicleEntry.EntryFactory vehicleEntryFactory, DrtRequestInsertionRetryQueue insertionRetryQueue, DrtInsertionSearch insertionSearch, - DrtOfferAcceptor drtOfferAcceptor, ForkJoinPool forkJoinPool) { + DrtOfferAcceptor drtOfferAcceptor, ForkJoinPool forkJoinPool, PassengerStopDurationProvider stopDurationProvider) { this.mode = mode; this.fleet = fleet; this.timeOfDay = timeOfDay; @@ -89,6 +91,7 @@ public DefaultUnplannedRequestInserter(DrtConfigGroup drtCfg, Fleet fleet, Mobsi this.insertionSearch = insertionSearch; this.drtOfferAcceptor = drtOfferAcceptor; this.forkJoinPool = forkJoinPool; + this.stopDurationProvider = stopDurationProvider; } @Override @@ -150,11 +153,17 @@ private void scheduleUnplannedRequest(DrtRequest req, Map, Vehic } else { vehicleEntries.remove(vehicle.getId()); } + + double expectedPickupTime = pickupDropoffTaskPair.pickupTask.getBeginTime(); + expectedPickupTime = Math.max(expectedPickupTime, acceptedRequest.get().getEarliestStartTime()); + expectedPickupTime += stopDurationProvider.calcPickupDuration(vehicle, req); + + double expectedDropoffTime = pickupDropoffTaskPair.dropoffTask.getBeginTime(); + expectedDropoffTime += stopDurationProvider.calcDropoffDuration(vehicle, req); eventsManager.processEvent( new PassengerRequestScheduledEvent(now, mode, req.getId(), req.getPassengerId(), vehicle.getId(), - pickupDropoffTaskPair.pickupTask.getEndTime(), - pickupDropoffTaskPair.dropoffTask.getBeginTime())); + expectedPickupTime, expectedDropoffTime)); } } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculator.java index 4e0006320cc..2de0d4dd640 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculator.java @@ -81,7 +81,8 @@ public DropoffDetourInfo calcDropoffDetourInfo(Insertion insertion, double toDro VehicleEntry vEntry = insertion.vehicleEntry; if (dropoff.newWaypoint.getLink() == dropoff.previousWaypoint.getLink()) { - double arrivalTime = dropoff.previousWaypoint.getArrivalTime() + pickupDetourInfo.pickupTimeLoss; + double remainingPickupTimeLoss = calculateRemainingPickupTimeLossAtDropoff(insertion, pickupDetourInfo); + double arrivalTime = dropoff.previousWaypoint.getArrivalTime() + remainingPickupTimeLoss; DrtStopTask stopTask = findStopTaskIfSameLinkAsPrevious(vEntry, dropoff.index); double departureTime = stopTimeCalculator.updateEndTimeForDropoff(vEntry.vehicle, stopTask, arrivalTime, drtRequest); @@ -93,7 +94,8 @@ public DropoffDetourInfo calcDropoffDetourInfo(Insertion insertion, double toDro } double toDropoffDepartureTime = dropoff.previousWaypoint.getDepartureTime(); - double arrivalTime = toDropoffDepartureTime + pickupDetourInfo.pickupTimeLoss + toDropoffTT; + double remainingPickupTimeLoss = calculateRemainingPickupTimeLossAtDropoff(insertion, pickupDetourInfo); + double arrivalTime = toDropoffDepartureTime + remainingPickupTimeLoss + toDropoffTT; double departureTime = stopTimeCalculator.initEndTimeForDropoff(vEntry.vehicle, arrivalTime, drtRequest); double stopDuration = departureTime - arrivalTime; double replacedDriveTT = calculateReplacedDriveDuration(vEntry, dropoff.index, toDropoffDepartureTime); @@ -188,7 +190,33 @@ private double calculateReplacedDriveDuration(VehicleEntry vEntry, int insertion double replacedDriveStartTime = vEntry.getWaypoint(insertionIdx).getDepartureTime(); double replacedDriveEndTime = vEntry.stops.get(insertionIdx).task.getBeginTime(); - return replacedDriveEndTime - replacedDriveStartTime; + + // reduce by the idle time before the next stop, to get the actual drive time + return replacedDriveEndTime - replacedDriveStartTime - vEntry.getPrecedingStayTime(insertionIdx); + } + + /* + * When inserting a pickup, we generate a "pickup loss" which describes by how + * much time we have to shift all following tasks to the future. + * + * In the case that some of the following stops are prebooked, however, there + * may be a stay time buffer between the insertion point and the stop. Hence, if + * a following stop only happens in four hours, we may not need to shift the + * task to the future. A preceding stay time, hence, reduces the introduced + * pickup loss. + * + * The present function calculates the remaining pickup loss at the dropoff + * insertion point after deducting all the stay times up to the dropoff. + */ + public static double calculateRemainingPickupTimeLossAtDropoff(Insertion insertion, PickupDetourInfo pickupDetourInfo) { + VehicleEntry vEntry = insertion.vehicleEntry; + double remainingPickupTimeLoss = pickupDetourInfo.pickupTimeLoss; + + for (int i = insertion.pickup.index + 1; i < insertion.dropoff.index; i++) { + remainingPickupTimeLoss = Math.max(remainingPickupTimeLoss - vEntry.getPrecedingStayTime(i), 0.0); + } + + return remainingPickupTimeLoss; } public static class PickupDetourInfo { diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java index 1316ccde366..df990058487 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java @@ -153,14 +153,42 @@ public List generateInsertions(DrtRequest drtRequest, V int stopCount = vEntry.stops.size(); List insertions = new ArrayList<>(); int occupancy = vEntry.start.occupancy; + for (int i = 0; i < stopCount; i++) {// insertions up to before last stop Waypoint.Stop nextStop = nextStop(vEntry, i); - - if (occupancy < vEntry.vehicle.getCapacity()) {// only not fully loaded arcs + + // (1) only not fully loaded arcs + boolean allowed = occupancy < vEntry.vehicle.getCapacity(); + + // (2) check if the request wants to depart after the departure time of the next + // stop. We can early on filter out the current insertion, because we will + // neither be able to insert our stop before the next stop nor merge the request + // into it. + allowed &= drtRequest.getEarliestStartTime() <= nextStop.getDepartureTime(); + + if (allowed) { if (drtRequest.getFromLink() != nextStop.task.getLink()) {// next stop at different link generateDropoffInsertions(drtRequest, vEntry, i, insertions); + } else { + // this is the case where we insert a new request *before* a stop that is + // on the same link as the pickup link. Initially, the reasoning was that the + // new request will be merged *into* the existing task if all constraints hold, + // i.e. the request will be appended. So only the insertion *after* this task is + // necessary to evaluate. However, with prebooking, the situation is different: + // if the next task is prebooked (in the future), we may want to insert another + // task here on the same link (maybe a pickup followed by its dropoff) but much + // earlier. In that case it is actually a valid insertion. + + if (drtRequest.getEarliestStartTime() < nextStop.getArrivalTime()) { + // the new request wants to depart before the start of the next stop, which may + // be a viable insertion. Note that if the requested wanted to depart after the + // start of the next stop, but before its end, this is a special case that is + // covered further downstream as a special case of merging the pickup into the + // existing stop task. + + generateDropoffInsertions(drtRequest, vEntry, i, insertions); + } } - // else: do not evaluate insertion _before_stop i, evaluate only insertion _after_ stop i } occupancy = nextStop.outgoingOccupancy; @@ -177,9 +205,10 @@ private void generateDropoffInsertions(DrtRequest request, VehicleEntry vEntry, double toPickupDepartureTime = pickupInsertion.previousWaypoint.getDepartureTime(); double toPickupTT = detourTimeEstimator.estimateTime(pickupInsertion.previousWaypoint.getLink(), request.getFromLink(), toPickupDepartureTime); + double earliestPickupStartTime = Math.max(toPickupDepartureTime + toPickupTT, request.getEarliestStartTime()); double fromPickupTT = detourTimeEstimator.estimateTime(request.getFromLink(), pickupInsertion.nextWaypoint.getLink(), - toPickupDepartureTime + toPickupTT); //TODO stopDuration not included + earliestPickupStartTime); //TODO stopDuration not included var pickupDetourInfo = detourTimeCalculator.calcPickupDetourInfo(vEntry, pickupInsertion, toPickupTT, fromPickupTT, true, request); @@ -210,7 +239,7 @@ private void generateDropoffInsertions(DrtRequest request, VehicleEntry vEntry, //calculate it once for all j > i pickupInsertion = createPickupInsertion(request, vEntry, i, false); fromPickupTT = detourTimeEstimator.estimateTime(request.getFromLink(), pickupInsertion.nextWaypoint.getLink(), - toPickupDepartureTime + toPickupTT); //TODO stopDuration not included + earliestPickupStartTime); //TODO stopDuration not included pickupDetourInfo = detourTimeCalculator.calcPickupDetourInfo(vEntry, pickupInsertion, toPickupTT, fromPickupTT, false, request); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequestCreator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequestCreator.java index 22a1fad6ceb..029bcf08239 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequestCreator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequestCreator.java @@ -53,7 +53,7 @@ public DrtRequest createRequest(Id id, Id passengerId, Route ro eventsManager.processEvent( new DrtRequestSubmittedEvent(submissionTime, mode, id, passengerId, fromLink.getId(), toLink.getId(), - drtRoute.getDirectRideTime(), drtRoute.getDistance(), latestDepartureTime, latestArrivalTime)); + drtRoute.getDirectRideTime(), drtRoute.getDistance(), departureTime, latestDepartureTime, latestArrivalTime)); DrtRequest request = DrtRequest.newBuilder() .id(id) diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/events/DrtRequestSubmittedEvent.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/events/DrtRequestSubmittedEvent.java index fe7d745c436..7bf6f23b343 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/events/DrtRequestSubmittedEvent.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/events/DrtRequestSubmittedEvent.java @@ -38,21 +38,24 @@ public class DrtRequestSubmittedEvent extends PassengerRequestSubmittedEvent { public static final String ATTRIBUTE_UNSHARED_RIDE_TIME = "unsharedRideTime"; public static final String ATTRIBUTE_UNSHARED_RIDE_DISTANCE = "unsharedRideDistance"; + public static final String ATTRIBUTE_EARLIEST_DEPARTURE_TIME = "earliestDepartureTime"; public static final String ATTRIBUTE_LATEST_PICKUP_TIME = "latestPickupTime"; public static final String ATTRIBUTE_LATEST_DROPOFF_TIME = "latestDropoffTime"; private final double unsharedRideTime; private final double unsharedRideDistance; + private final double earliestDepartureTime; private final double latestPickupTime; private final double latestDropoffTime; public DrtRequestSubmittedEvent(double time, String mode, Id requestId, Id personId, Id fromLinkId, Id toLinkId, double unsharedRideTime, double unsharedRideDistance, - double latestPickupTime, double latestDropoffTime) { + double earliestDepartureTime, double latestPickupTime, double latestDropoffTime) { super(time, mode, requestId, personId, fromLinkId, toLinkId); this.unsharedRideTime = unsharedRideTime; this.unsharedRideDistance = unsharedRideDistance; + this.earliestDepartureTime = earliestDepartureTime; this.latestPickupTime = latestPickupTime; this.latestDropoffTime = latestDropoffTime; } @@ -76,6 +79,10 @@ public final double getUnsharedRideDistance() { return unsharedRideDistance; } + public final double getEarliestDepartureTime() { + return earliestDepartureTime; + } + public final double getLatestPickupTime() { return latestPickupTime; } @@ -89,6 +96,7 @@ public Map getAttributes() { Map attr = super.getAttributes(); attr.put(ATTRIBUTE_UNSHARED_RIDE_TIME, unsharedRideTime + ""); attr.put(ATTRIBUTE_UNSHARED_RIDE_DISTANCE, unsharedRideDistance + ""); + attr.put(ATTRIBUTE_EARLIEST_DEPARTURE_TIME, earliestDepartureTime + ""); attr.put(ATTRIBUTE_LATEST_PICKUP_TIME, latestPickupTime + ""); attr.put(ATTRIBUTE_LATEST_DROPOFF_TIME, latestDropoffTime + ""); return attr; @@ -104,9 +112,10 @@ public static DrtRequestSubmittedEvent convert(GenericEvent event) { Id toLinkId = Id.createLinkId(attributes.get(ATTRIBUTE_TO_LINK)); double unsharedRideTime = Double.parseDouble(attributes.get(ATTRIBUTE_UNSHARED_RIDE_TIME)); double unsharedRideDistance = Double.parseDouble(attributes.get(ATTRIBUTE_UNSHARED_RIDE_DISTANCE)); + double earliestDepartureTime = Double.parseDouble(attributes.getOrDefault(ATTRIBUTE_EARLIEST_DEPARTURE_TIME, "NaN")); double latestPickupTime = Double.parseDouble(attributes.getOrDefault(ATTRIBUTE_LATEST_PICKUP_TIME, "NaN")); double latestDropoffTime = Double.parseDouble(attributes.getOrDefault(ATTRIBUTE_LATEST_DROPOFF_TIME, "NaN")); return new DrtRequestSubmittedEvent(time, mode, requestId, personId, fromLinkId, toLinkId, unsharedRideTime, - unsharedRideDistance, latestPickupTime, latestDropoffTime); + unsharedRideDistance, earliestDepartureTime, latestPickupTime, latestDropoffTime); } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEvent.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEvent.java new file mode 100644 index 00000000000..9b8ce42dd25 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEvent.java @@ -0,0 +1,35 @@ +package org.matsim.contrib.drt.prebooking; + +import java.util.Map; +import java.util.Objects; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.population.Person; +import org.matsim.contrib.dvrp.optimizer.Request; +import org.matsim.contrib.dvrp.passenger.AbstractPassengerRequestEvent; + +/** + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class PassengerRequestBookedEvent extends AbstractPassengerRequestEvent { + public static final String EVENT_TYPE = "PassengerRequest booked"; + + public PassengerRequestBookedEvent(double time, String mode, Id requestId, Id personId) { + super(time, mode, requestId, personId); + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + public static PassengerRequestBookedEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + String mode = Objects.requireNonNull(attributes.get(ATTRIBUTE_MODE)); + Id requestId = Id.create(attributes.get(ATTRIBUTE_REQUEST), Request.class); + Id personId = Id.createPersonId(attributes.get(ATTRIBUTE_PERSON)); + return new PassengerRequestBookedEvent(time, mode, requestId, personId); + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEventHandler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEventHandler.java new file mode 100644 index 00000000000..19a47c3fb9c --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEventHandler.java @@ -0,0 +1,10 @@ +package org.matsim.contrib.drt.prebooking; + +import org.matsim.core.events.handler.EventHandler; + +/** + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public interface PassengerRequestBookedEventHandler extends EventHandler { + void handleEvent(final PassengerRequestBookedEvent event); +} 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 new file mode 100644 index 00000000000..d13e62630ad --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingActionCreator.java @@ -0,0 +1,50 @@ +package org.matsim.contrib.drt.prebooking; + +import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.getBaseTypeOrElseThrow; + +import org.matsim.contrib.drt.schedule.DrtStopTask; +import org.matsim.contrib.drt.schedule.DrtTaskBaseType; +import org.matsim.contrib.drt.stops.PassengerStopDurationProvider; +import org.matsim.contrib.drt.vrpagent.DrtActionCreator; +import org.matsim.contrib.dvrp.fleet.DvrpVehicle; +import org.matsim.contrib.dvrp.passenger.PassengerHandler; +import org.matsim.contrib.dvrp.schedule.Task; +import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic; +import org.matsim.contrib.dynagent.DynAction; +import org.matsim.contrib.dynagent.DynAgent; + +/** + * For prebooking, we implement an alternative DynActivity that handles entering + * / exiting passengers more flexibly than the standard DrtStopActivity. + * + * Specifically, we track when a person is ready for departure and then add the + * expected duration of the interaction into a queue. The agent only actually + * enters the vehicle after this time has elapsed. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class PrebookingActionCreator implements VrpAgentLogic.DynActionCreator { + private final VrpAgentLogic.DynActionCreator delegate; + private final PassengerHandler passengerHandler; + private final PassengerStopDurationProvider stopDurationProvider; + + public PrebookingActionCreator(PassengerHandler passengerHandler, VrpAgentLogic.DynActionCreator delegate, + PassengerStopDurationProvider stopDurationProvider) { + this.delegate = delegate; + this.passengerHandler = passengerHandler; + this.stopDurationProvider = stopDurationProvider; + } + + @Override + public DynAction createAction(DynAgent dynAgent, DvrpVehicle vehicle, double now) { + Task task = vehicle.getSchedule().getCurrentTask(); + + 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); + } + + 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 new file mode 100644 index 00000000000..3c25ff15430 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java @@ -0,0 +1,212 @@ +package org.matsim.contrib.drt.prebooking; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.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.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.PassengerRequestValidator; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.framework.MobsimAgent.State; +import org.matsim.core.mobsim.qsim.InternalInterface; +import org.matsim.core.mobsim.qsim.agents.WithinDayAgentUtils; +import org.matsim.core.mobsim.qsim.interfaces.MobsimEngine; + +import com.google.common.base.Preconditions; + +/** + * This class manages prebooked requests. One instance of PrebookingManager + * exists per mode. The entry point is PrebookingManager::prebook to which you + * need to pass a person, a leg with the respective DRT mode, the + * requested/expected earliest departure time, and the time at which the request + * should be submitted / taken into account in the system. + * + * Preplanned requests can be submitted any time before the planned + * departure/submission times. + * + * Internally, the prebooking manager will create a request identifier and + * return the request once the agent actually wants to depart on the planned + * leg. The link between a leg and a request is managed by inserting a special + * attribute in the leg instance. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class PrebookingManager implements MobsimEngine, AdvanceRequestProvider { + private final String mode; + + private final Network network; + private final EventsManager eventsManager; + + private final VrpOptimizer optimizer; + + public PrebookingManager(String mode, Network network, PassengerRequestCreator requestCreator, + VrpOptimizer optimizer, PassengerRequestValidator requestValidator, EventsManager eventsManager) { + this.network = network; + this.mode = mode; + this.requestCreator = requestCreator; + this.optimizer = optimizer; + this.requestAttribute = PREBOOKED_REQUEST_PREFIX + ":" + mode; + this.requestValidator = requestValidator; + this.eventsManager = eventsManager; + } + + // Functionality for ID management + + private static final String PREBOOKED_REQUEST_PREFIX = "prebookedRequestId"; + private final AtomicInteger currentRequestIndex = new AtomicInteger(-1); + private final String requestAttribute; + + private Id createRequestId() { + return Id.create(mode + "_prebooked_" + currentRequestIndex.incrementAndGet(), Request.class); + } + + public boolean isPrebookedRequest(Id requestId) { + return requestId.toString().startsWith(mode + "_prebooked_"); + } + + public Id getRequestId(Leg leg) { + String rawRequestId = (String) leg.getAttributes().getAttribute(requestAttribute); + + if (rawRequestId == null) { + return null; + } + + return Id.create(rawRequestId, Request.class); + } + + // Booking functionality + + private final PassengerRequestCreator requestCreator; + private final PassengerRequestValidator requestValidator; + private final List queue = 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)); + } + } + + 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(); + + eventsManager.processEvent(new PassengerRequestBookedEvent(now, mode, requestId, item.person.getId())); + + 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); + + 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 (!violations.isEmpty()) { + String cause = String.join(", ", violations); + eventsManager.processEvent(new PassengerRequestRejectedEvent(now, mode, request.getId(), + request.getPassengerId(), cause)); + } else { + synchronized (optimizer) { + optimizer.requestSubmitted(request); + } + + item.leg.getAttributes().putAttribute(requestAttribute, request.getId().toString()); + requests.put(requestId, new RequestItem(request)); + } + } + + queue.clear(); + } + } + + private Link getLink(Id linkId) { + return Preconditions.checkNotNull(network.getLinks().get(linkId), + "Link id=%s does not exist in network for mode %s. Agent departs from a link that does not belong to that network?", + linkId, mode); + } + + private record QueueItem(MobsimAgent person, Leg leg, double earliestDepartureTime) { + } + + // Interface with PassengerEngine + + @Override + @Nullable + public PassengerRequest retrieveRequest(MobsimAgent agent, Leg leg) { + Preconditions.checkArgument(leg.getMode().equals(mode), "Invalid mode for this prebooking manager"); + + Id requestId = getRequestId(leg); + + if (requestId == null) { + return null; + } + + RequestItem item = requests.remove(requestId); + + if (item == null) { + return null; + } + + return item.request; + } + + // Housekeeping of requests + + private IdMap requests = new IdMap<>(Request.class); + + private class RequestItem { + // this class looks minimal for now, but will be extended with canceling + // functionality + final PassengerRequest request; + + RequestItem(PassengerRequest request) { + this.request = request; + } + } + + // Engine code + + @Override + public void doSimStep(double now) { + processQueue(now); + } + + @Override + public void onPrepareSim() { + } + + @Override + public void afterSim() { + } + + @Override + public void setInternalInterface(InternalInterface internalInterface) { + } +} 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 new file mode 100644 index 00000000000..4c423d64ce4 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java @@ -0,0 +1,56 @@ +package org.matsim.contrib.drt.prebooking; + +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.population.Population; +import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator.PopulationIteratorFactory; +import org.matsim.contrib.drt.prebooking.logic.helpers.PrebookingQueue; +import org.matsim.contrib.drt.stops.PassengerStopDurationProvider; +import org.matsim.contrib.drt.vrpagent.DrtActionCreator; +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.core.api.experimental.events.EventsManager; +import org.matsim.core.mobsim.qsim.QSim; + +import com.google.inject.Singleton; + +public class PrebookingModeQSimModule extends AbstractDvrpModeQSimModule { + public PrebookingModeQSimModule(String mode) { + super(mode); + } + + @Override + protected void configureQSim() { + bindModal(PrebookingActionCreator.class).toProvider(modalProvider(getter -> { + PassengerHandler passengerHandler = (PassengerEngine) getter.getModal(PassengerHandler.class); + DrtActionCreator delegate = getter.getModal(DrtActionCreator.class); + PassengerStopDurationProvider stopDurationProvider = getter.getModal(PassengerStopDurationProvider.class); + + return new PrebookingActionCreator(passengerHandler, delegate, stopDurationProvider); + })).in(Singleton.class); + + bindModal(PrebookingManager.class).toProvider(modalProvider(getter -> { + Network network = getter.getModal(Network.class); + PassengerRequestCreator requestCreator = getter.getModal(PassengerRequestCreator.class); + VrpOptimizer optimizer = getter.getModal(VrpOptimizer.class); + PassengerRequestValidator requestValidator = getter.getModal(PassengerRequestValidator.class); + EventsManager eventsManager = getter.get(EventsManager.class); + + return new PrebookingManager(getMode(), network, requestCreator, optimizer, requestValidator, + eventsManager); + })).in(Singleton.class); + addModalQSimComponentBinding().to(modalKey(PrebookingManager.class)); + + bindModal(PrebookingQueue.class).toProvider(modalProvider(getter -> { + return new PrebookingQueue(getter.getModal(PrebookingManager.class)); + })).in(Singleton.class); + addModalQSimComponentBinding().to(modalKey(PrebookingQueue.class)); + + bindModal(PopulationIteratorFactory.class).toProvider(modalProvider(getter -> { + return new PopulationIteratorFactory(getter.get(Population.class), getter.get(QSim.class)); + })); + } +} 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 new file mode 100644 index 00000000000..86376f51a67 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java @@ -0,0 +1,20 @@ +package org.matsim.contrib.drt.prebooking; + +import org.matsim.core.config.ReflectiveConfigGroup; + +public class PrebookingParams extends ReflectiveConfigGroup { + public static final String SET_NAME = "prebooking"; + + public PrebookingParams() { + super(SET_NAME); + } + + @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.") + 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 +} \ 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 new file mode 100644 index 00000000000..e83d03cf679 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java @@ -0,0 +1,143 @@ +package org.matsim.contrib.drt.prebooking; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.population.Person; +import org.matsim.contrib.drt.passenger.AcceptedDrtRequest; +import org.matsim.contrib.drt.stops.PassengerStopDurationProvider; +import org.matsim.contrib.dvrp.fleet.DvrpVehicle; +import org.matsim.contrib.dvrp.optimizer.Request; +import org.matsim.contrib.dvrp.passenger.PassengerHandler; +import org.matsim.contrib.dvrp.passenger.PassengerPickupActivity; +import org.matsim.contrib.dvrp.schedule.StayTask; +import org.matsim.contrib.dynagent.DynAgent; +import org.matsim.contrib.dynagent.FirstLastSimStepDynActivity; +import org.matsim.core.mobsim.framework.MobsimPassengerAgent; + +import com.google.common.base.Verify; + +/** + * Modified version of DrtStopActivity which handles parallel mounting and + * alighting of passengers according to individual pickup and dropoff times. + * + * @author Sebastian Hörl, IRT SystemX (sebhoerl) + */ +public class PrebookingStopActivity extends FirstLastSimStepDynActivity implements PassengerPickupActivity { + private final DynAgent driver; + private final DvrpVehicle vehicle; + + private final Map, ? extends AcceptedDrtRequest> pickupRequests; + private final Map, ? extends AcceptedDrtRequest> dropoffRequests; + + private final IdMap enterTimes = new IdMap<>(Request.class); + private final IdMap leaveTimes = new IdMap<>(Request.class); + private final Set> enteredRequests = new HashSet<>(); + + private final PassengerHandler passengerHandler; + + private final PassengerStopDurationProvider stopDurationProvider; + 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) { + super(activityType); + this.passengerHandler = passengerHandler; + this.driver = driver; + this.dropoffRequests = dropoffRequests; + this.pickupRequests = pickupRequests; + this.stopDurationProvider = stopDurationProvider; + this.vehicle = vehicle; + this.endTime = endTime; + } + + @Override + protected boolean isLastStep(double now) { + return updatePickupRequests(now) && leaveTimes.size() == 0 && now >= endTime.get(); + } + + @Override + protected void beforeFirstStep(double now) { + initDropoffRequests(now); + updatePickupRequests(now); + } + + private void initDropoffRequests(double now) { + for (var request : dropoffRequests.values()) { + double leaveTime = now + stopDurationProvider.calcDropoffDuration(vehicle, request.getRequest()); + leaveTimes.put(request.getId(), leaveTime); + } + + processDropoffRequests(now); + } + + private void processDropoffRequests(double now) { + var iterator = leaveTimes.entrySet().iterator(); + + while (iterator.hasNext()) { + var entry = iterator.next(); + + if (entry.getValue() <= now) { // Request should leave now + passengerHandler.dropOffPassenger(driver, entry.getKey(), now); + iterator.remove(); + } + } + } + + private boolean updatePickupRequests(double now) { + for (var request : pickupRequests.values()) { + if (!enteredRequests.contains(request.getId()) && !enterTimes.containsKey(request.getId())) { + // this is a new request that has been added after the activity has been created + // or that had not arrived yet + + if (passengerHandler.notifyWaitForPassenger(this, this.driver, request.getId())) { + // agent starts to enter + queuePickup(request, now); + } + } + } + + var iterator = enterTimes.entrySet().iterator(); + + while (iterator.hasNext()) { + var entry = iterator.next(); + + if (entry.getValue() <= now) { + // let agent enter now + Verify.verify(passengerHandler.tryPickUpPassenger(this, driver, entry.getKey(), now)); + enteredRequests.add(entry.getKey()); + iterator.remove(); + } + } + + return enterTimes.size() == 0 && enteredRequests.size() == pickupRequests.size(); + } + + private void queuePickup(AcceptedDrtRequest request, double now) { + double enterTime = now + stopDurationProvider.calcPickupDuration(vehicle, request.getRequest()); + enterTimes.put(request.getId(), enterTime); + } + + @Override + protected void simStep(double now) { + // dynamics are handled in isLastStep -> updatePickupRequests + } + + @Override + public void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, double now) { + var request = getRequestForPassenger(passenger.getId()); + queuePickup(request, now); + } + + private AcceptedDrtRequest getRequestForPassenger(Id passengerId) { + return pickupRequests.values().stream().filter(r -> passengerId.equals(r.getPassengerId())).findAny() + .orElseThrow(() -> new IllegalArgumentException("I am waiting for different passengers!")); + } +} 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 new file mode 100644 index 00000000000..59c6f002974 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisHandler.java @@ -0,0 +1,117 @@ +package org.matsim.contrib.drt.prebooking.analysis; + +import java.util.LinkedList; +import java.util.List; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.population.Person; +import org.matsim.contrib.drt.prebooking.PassengerRequestBookedEvent; +import org.matsim.contrib.drt.prebooking.PassengerRequestBookedEventHandler; +import org.matsim.contrib.dvrp.optimizer.Request; +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.PassengerRequestSubmittedEvent; +import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEventHandler; +import org.matsim.contrib.dvrp.passenger.PassengerWaitingEvent; +import org.matsim.contrib.dvrp.passenger.PassengerWaitingEventHandler; + +public class PrebookingAnalysisHandler implements PassengerRequestBookedEventHandler, + PassengerRequestSubmittedEventHandler, PassengerRequestScheduledEventHandler, + PassengerRequestRejectedEventHandler, PassengerWaitingEventHandler { + private final String mode; + private final IdMap sequences = new IdMap<>(Request.class); + + public PrebookingAnalysisHandler(String mode) { + this.mode = mode; + } + + @Override + public void handleEvent(PassengerRequestBookedEvent event) { + sequences.put(event.getRequestId(), new Sequence(event)); + } + + @Override + public void handleEvent(PassengerRequestSubmittedEvent event) { + if (!event.getMode().equals(mode)) { + return; + } + + Sequence sequence = sequences.get(event.getRequestId()); + + if (sequence != null) { + sequence.submitted = event; + } + } + + @Override + public void handleEvent(PassengerRequestRejectedEvent event) { + if (!event.getMode().equals(mode)) { + return; + } + + Sequence sequence = sequences.get(event.getRequestId()); + + if (sequence != null) { + sequence.rejected = event; + } + } + + @Override + public void handleEvent(PassengerRequestScheduledEvent event) { + if (!event.getMode().equals(mode)) { + return; + } + + Sequence sequence = sequences.get(event.getRequestId()); + + if (sequence != null) { + sequence.scheduled = event; + } + } + + @Override + public void handleEvent(PassengerWaitingEvent event) { + if (!event.getMode().equals(mode)) { + return; + } + + Sequence sequence = sequences.get(event.getRequestId()); + + if (sequence != null) { + sequence.waiting = event; + } + } + + public List getRecords() { + List records = new LinkedList<>(); + + for (Sequence sequence : sequences) { + records.add(new RequestRecord(sequence.booked.getRequestId(), sequence.booked.getPersonId(), + 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)); + } + + return records; + } + + public record RequestRecord(Id requestId, Id personId, Double submissionTime, Double scheduledTime, + Double rejectedTime, Double enteringTime) { + } + + private class Sequence { + final PassengerRequestBookedEvent booked; + PassengerRequestSubmittedEvent submitted; + PassengerRequestScheduledEvent scheduled; + PassengerRequestRejectedEvent rejected; + PassengerWaitingEvent waiting; + + Sequence(PassengerRequestBookedEvent booked) { + this.booked = booked; + } + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisListener.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisListener.java new file mode 100644 index 00000000000..f90dab2c2ce --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisListener.java @@ -0,0 +1,42 @@ +package org.matsim.contrib.drt.prebooking.analysis; + +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.events.IterationStartsEvent; +import org.matsim.core.controler.listener.IterationEndsListener; +import org.matsim.core.controler.listener.IterationStartsListener; + +public class PrebookingAnalysisListener implements IterationStartsListener, IterationEndsListener { + private final String mode; + + private final OutputDirectoryHierarchy outputHierarchy; + private final EventsManager eventsManager; + + private PrebookingAnalysisHandler handler; + + public PrebookingAnalysisListener(String mode, EventsManager eventsManager, + OutputDirectoryHierarchy outputHierarchy) { + this.mode = mode; + this.eventsManager = eventsManager; + this.outputHierarchy = outputHierarchy; + } + + @Override + public void notifyIterationStarts(IterationStartsEvent event) { + handler = new PrebookingAnalysisHandler(mode); + eventsManager.addHandler(handler); + } + + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + eventsManager.removeHandler(handler); + + String outputPath = outputHierarchy.getIterationFilename(event.getIteration(), getOutputFileName()); + new PrebookingAnalysisWriter(outputPath).write(handler.getRecords()); + } + + private String getOutputFileName() { + return String.format("prebooking_%s.csv", mode); + } +} 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 new file mode 100644 index 00000000000..f8fe4eb612f --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisWriter.java @@ -0,0 +1,45 @@ +package org.matsim.contrib.drt.prebooking.analysis; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.List; + +import org.matsim.core.utils.io.IOUtils; + +public class PrebookingAnalysisWriter { + private final String outputPath; + + public PrebookingAnalysisWriter(String outputPath) { + this.outputPath = outputPath; + } + + public void write(List records) { + try { + BufferedWriter writer = IOUtils.getBufferedWriter(outputPath); + + writer.write(String.join(",", new String[] { // + "request_id", // + "person_id", // + "submission_time", // + "scheduled_time", // + "rejected_time", // + "entering_time" // + }) + "\n"); + + for (var record : records) { + writer.write(String.join(",", new String[] { // + record.requestId().toString(), // + record.personId().toString(), // + 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()) // + }) + "\n"); + } + + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingModeAnalysisModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingModeAnalysisModule.java new file mode 100644 index 00000000000..5382f928d2d --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingModeAnalysisModule.java @@ -0,0 +1,25 @@ +package org.matsim.contrib.drt.prebooking.analysis; + +import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.controler.OutputDirectoryHierarchy; + +import com.google.inject.Singleton; + +public class PrebookingModeAnalysisModule extends AbstractDvrpModeModule { + public PrebookingModeAnalysisModule(String mode) { + super(mode); + } + + @Override + public void install() { + addControlerListenerBinding().to(modalKey(PrebookingAnalysisListener.class)); + + bindModal(PrebookingAnalysisListener.class).toProvider(modalProvider(getter -> { + EventsManager eventsManager = getter.get(EventsManager.class); + OutputDirectoryHierarchy outputHierarchy = getter.get(OutputDirectoryHierarchy.class); + + return new PrebookingAnalysisListener(getMode(), eventsManager, outputHierarchy); + })).in(Singleton.class); + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/AdaptivePrebookingLogic.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/AdaptivePrebookingLogic.java new file mode 100644 index 00000000000..349000a5ac7 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/AdaptivePrebookingLogic.java @@ -0,0 +1,118 @@ +package org.matsim.contrib.drt.prebooking.logic; + +import org.matsim.api.core.v01.events.ActivityStartEvent; +import org.matsim.api.core.v01.events.handler.ActivityStartEventHandler; +import org.matsim.api.core.v01.population.Activity; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contrib.drt.prebooking.PrebookingManager; +import org.matsim.contrib.drt.prebooking.logic.helpers.PrebookingQueue; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; +import org.matsim.core.controler.Controler; +import org.matsim.core.events.MobsimScopeEventHandler; +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.qsim.QSim; +import org.matsim.core.mobsim.qsim.agents.WithinDayAgentUtils; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.utils.timing.TimeInterpretation; +import org.matsim.core.utils.timing.TimeTracker; + +import com.google.common.base.Preconditions; + +/** + * This is a prebooking logic that, whenever an agent starts an activity, checks + * whether there is a DRT leg coming up before the next main (non-stage) + * activity. If so, the upcoming leg is prebooked in advance with the + * submissionSlack parameter defining how much in advance. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class AdaptivePrebookingLogic implements PrebookingLogic, ActivityStartEventHandler, MobsimScopeEventHandler { + private final QSim qsim; + private final PrebookingManager prebookingManager; + private final PrebookingQueue prebookingQueue; + private final TimeInterpretation timeInterpretation; + + private final String mode; + + private final double submissionSlack; + + private AdaptivePrebookingLogic(String mode, QSim qsim, PrebookingManager prebookingManager, + PrebookingQueue prebookingQueue, TimeInterpretation timeInterpretation, double submissionSlack) { + this.prebookingManager = prebookingManager; + this.prebookingQueue = prebookingQueue; + this.qsim = qsim; + this.mode = mode; + this.timeInterpretation = timeInterpretation; + this.submissionSlack = submissionSlack; + } + + @Override + public void handleEvent(ActivityStartEvent event) { + MobsimAgent agent = qsim.getAgents().get(event.getPersonId()); + + Plan plan = WithinDayAgentUtils.getModifiablePlan(agent); + int planElementIndex = WithinDayAgentUtils.getCurrentPlanElementIndex(agent); + + TimeTracker timeTracker = new TimeTracker(timeInterpretation); + timeTracker.setTime(event.getTime()); + + /* + * Now we starting to traverse the remaining plan to see if we find a DRT leg + * that comes after the currently started activity. If so, we prebook it with + * the configured submission slack. We start searching one we reach the next + * non-stage activity type. + */ + + for (int i = planElementIndex + 1; i < plan.getPlanElements().size(); i++) { + PlanElement element = plan.getPlanElements().get(i); + + if (element instanceof Activity) { + Activity activity = (Activity) element; + + if (!TripStructureUtils.isStageActivityType(activity.getType())) { + break; // only consider legs coming directly after the ongoing activity + } + } else if (element instanceof Leg) { + Leg leg = (Leg) element; + + if (mode.equals(leg.getMode())) { + double departureTime = timeTracker.getTime().seconds(); + + if (prebookingManager.getRequestId(leg) == null) { + // only book legs that are not already prebooked + + double submissionTime = Math.max(event.getTime(), departureTime - submissionSlack); + if (submissionTime > departureTime) { + prebookingQueue.schedule(submissionTime, agent, leg, departureTime); + } + } + } + } + + timeTracker.addElement(element); + } + } + + static public AbstractDvrpModeQSimModule createModule(DrtConfigGroup drtConfig, double subissionSlack) { + return new AbstractDvrpModeQSimModule(drtConfig.getMode()) { + @Override + protected void configureQSim() { + bindModal(AdaptivePrebookingLogic.class).toProvider(modalProvider(getter -> { + Preconditions.checkState(drtConfig.getPrebookingParams().isPresent()); + + return new AdaptivePrebookingLogic(drtConfig.getMode(), getter.get(QSim.class), + getter.getModal(PrebookingManager.class), getter.getModal(PrebookingQueue.class), + getter.get(TimeInterpretation.class), subissionSlack); + })); + addMobsimScopeEventHandlerBinding().to(modalKey(AdaptivePrebookingLogic.class)); + } + }; + } + + static public void install(Controler controller, DrtConfigGroup drtConfig, double subissionSlack) { + controller.addOverridingQSimModule(createModule(drtConfig, subissionSlack)); + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/AttributeBasedPrebookingLogic.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/AttributeBasedPrebookingLogic.java new file mode 100644 index 00000000000..a48cbe50e46 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/AttributeBasedPrebookingLogic.java @@ -0,0 +1,137 @@ +package org.matsim.contrib.drt.prebooking.logic; + +import java.util.Optional; + +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator; +import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator.PopulationIteratorFactory; +import org.matsim.contrib.drt.prebooking.logic.helpers.PrebookingQueue; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; +import org.matsim.core.controler.Controler; +import org.matsim.core.mobsim.framework.events.MobsimInitializedEvent; +import org.matsim.core.mobsim.framework.listeners.MobsimInitializedListener; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.router.TripStructureUtils.Trip; +import org.matsim.core.utils.timing.TimeInterpretation; +import org.matsim.core.utils.timing.TimeTracker; + +import com.google.common.base.Preconditions; + +/** + * This class represents a prebooking logic that will search for the + * "prebooking:submissionTime:[mode]" attribute in the origin activity of a + * trip. If a DRT leg is found in the trip, the submission time for this request + * will be read from the attribute. In order to make use of prebooked requests, + * you need to define these attributes before the start of the QSim iteration. + * + * To indicate the expected departure time (for which the vehicle is requested), + * you can also define the "prebooking:plannedDepartureTime:[mode]" attribute. + * If it is not specified, the logic will try to figure out the departure time + * by traversing the activities and legs according to the configured time + * interpretation. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class AttributeBasedPrebookingLogic implements PrebookingLogic, MobsimInitializedListener { + static private final String SUBMISSION_TIME_ATTRIBUTE_PREFIX = "prebooking:submissionTime"; + static private final String PLANNED_DEPARTURE_ATTRIBUTE_PREFIX = "prebooking:plannedDepartureTime"; + + static public String getSubmissionAttribute(String mode) { + return SUBMISSION_TIME_ATTRIBUTE_PREFIX + mode; + } + + static public String getPlannedDepartureAttribute(String mode) { + return PLANNED_DEPARTURE_ATTRIBUTE_PREFIX + mode; + } + + static public Optional getSubmissionTime(String mode, Trip trip) { + return Optional.ofNullable((Double) trip.getTripAttributes().getAttribute(getSubmissionAttribute(mode))); + } + + static public Optional getPlannedDepartureTime(String mode, Trip trip) { + return Optional.ofNullable((Double) trip.getTripAttributes().getAttribute(getPlannedDepartureAttribute(mode))); + } + + private final PrebookingQueue prebookingQueue; + private final PopulationIteratorFactory populationIteratorFactory; + private final TimeInterpretation timeInterpretation; + + private final String mode; + + private AttributeBasedPrebookingLogic(String mode, PrebookingQueue prebookingQueue, + PopulationIteratorFactory populationIteratorFactory, TimeInterpretation timeInterpretation) { + this.prebookingQueue = prebookingQueue; + this.populationIteratorFactory = populationIteratorFactory; + this.mode = mode; + this.timeInterpretation = timeInterpretation; + + } + + @Override + public void notifyMobsimInitialized(@SuppressWarnings("rawtypes") MobsimInitializedEvent e) { + PopulationIterator populationIterator = populationIteratorFactory.create(); + + while (populationIterator.hasNext()) { + var personItem = populationIterator.next(); + + TimeTracker timeTracker = new TimeTracker(timeInterpretation); + + for (Trip trip : TripStructureUtils.getTrips(personItem.plan())) { + timeTracker.addActivity(trip.getOriginActivity()); + boolean foundLeg = false; + + for (PlanElement element : trip.getTripElements()) { + if (element instanceof Leg) { + Leg leg = (Leg) element; + + if (mode.equals(leg.getMode())) { + Preconditions.checkState(!foundLeg, + "Attribute-based prebooking logic only works with one DRT leg per trip"); + foundLeg = true; + + Optional submissionTime = getSubmissionTime(mode, trip); + Optional plannedDepartureTime = getPlannedDepartureTime(mode, trip); + + if (submissionTime.isPresent()) { + if (plannedDepartureTime.isPresent()) { + Preconditions.checkState(plannedDepartureTime.get() > submissionTime.get(), + "Planned departure time must be after submission time"); + } + + prebookingQueue.schedule(submissionTime.get(), personItem.agent(), leg, + plannedDepartureTime.orElse(timeTracker.getTime().seconds())); + } + } + } + + timeTracker.addElement(element); + } + + } + } + + prebookingQueue.performInitialSubmissions(); + } + + static public AbstractDvrpModeQSimModule createModule(DrtConfigGroup drtConfig) { + return new AbstractDvrpModeQSimModule(drtConfig.getMode()) { + @Override + protected void configureQSim() { + bindModal(AttributeBasedPrebookingLogic.class).toProvider(modalProvider(getter -> { + Preconditions.checkState(drtConfig.getPrebookingParams().isPresent()); + + return new AttributeBasedPrebookingLogic(drtConfig.getMode(), + getter.getModal(PrebookingQueue.class), getter.getModal(PopulationIteratorFactory.class), + getter.get(TimeInterpretation.class)); + })); + addModalQSimComponentBinding().to(modalKey(AttributeBasedPrebookingLogic.class)); + } + }; + } + + static public void install(Controler controller, DrtConfigGroup drtConfig) { + controller.addOverridingQSimModule(createModule(drtConfig)); + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PersonBasedPrebookingLogic.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PersonBasedPrebookingLogic.java new file mode 100644 index 00000000000..a7faed87853 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PersonBasedPrebookingLogic.java @@ -0,0 +1,113 @@ +package org.matsim.contrib.drt.prebooking.logic; + +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator; +import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator.PopulationIteratorFactory; +import org.matsim.contrib.drt.prebooking.logic.helpers.PrebookingQueue; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; +import org.matsim.core.controler.Controler; +import org.matsim.core.mobsim.framework.events.MobsimInitializedEvent; +import org.matsim.core.mobsim.framework.listeners.MobsimInitializedListener; +import org.matsim.core.utils.timing.TimeInterpretation; +import org.matsim.core.utils.timing.TimeTracker; + +import com.google.common.base.Preconditions; + +/** + * This is a prebooking logic that detects a person-level attribute which + * indicates whether requests to a certain DRT mode for that person are + * prebooked. It can be installed by calling + * + * PersonBasedPrebookingLogic.install + * + * on the controller with the respective DRT mode and a value indicating the + * prebooking slack, i.e., the time between the expected departure and when the + * request is submitted. + */ +public class PersonBasedPrebookingLogic implements PrebookingLogic, MobsimInitializedListener { + static private final String ATTRIBUTE_PREFIX = "prebooking:"; + + static public String getPersonAttribute(String mode) { + return ATTRIBUTE_PREFIX + mode; + } + + static public String getPersonAttribute(DrtConfigGroup drtConfig) { + return getPersonAttribute(drtConfig.getMode()); + } + + static public boolean isPrebooked(String mode, Person person) { + Boolean isPrebooked = (Boolean) person.getAttributes().getAttribute(getPersonAttribute(mode)); + return isPrebooked == true; + } + + private final PrebookingQueue prebookingQueue; + private final PopulationIteratorFactory populationIteratorFactory; + private final TimeInterpretation timeInterpretation; + + private final String mode; + private final double submissionSlack; + + private PersonBasedPrebookingLogic(String mode, PrebookingQueue prebookingQueue, + PopulationIteratorFactory populationIteratorFactory, TimeInterpretation timeInterpretation, + double submissionSlack) { + this.prebookingQueue = prebookingQueue; + this.populationIteratorFactory = populationIteratorFactory; + this.mode = mode; + this.timeInterpretation = timeInterpretation; + this.submissionSlack = submissionSlack; + + } + + @Override + public void notifyMobsimInitialized(@SuppressWarnings("rawtypes") MobsimInitializedEvent e) { + PopulationIterator populationIterator = populationIteratorFactory.create(); + + while (populationIterator.hasNext()) { + var personItem = populationIterator.next(); + + if (isPrebooked(mode, personItem.plan().getPerson())) { + TimeTracker timeTracker = new TimeTracker(timeInterpretation); + + for (PlanElement element : personItem.plan().getPlanElements()) { + if (element instanceof Leg) { + Leg leg = (Leg) element; + + if (leg.getMode().equals(mode)) { + double earliestDepartureTime = leg.getDepartureTime().seconds(); + double submissionTime = earliestDepartureTime - submissionSlack; + + prebookingQueue.schedule(submissionTime, personItem.agent(), leg, earliestDepartureTime); + } + } + + timeTracker.addElement(element); + } + } + } + + prebookingQueue.performInitialSubmissions(); + } + + static public AbstractDvrpModeQSimModule createModule(DrtConfigGroup drtConfig, double prebookingSlack) { + return new AbstractDvrpModeQSimModule(drtConfig.getMode()) { + @Override + protected void configureQSim() { + bindModal(PersonBasedPrebookingLogic.class).toProvider(modalProvider(getter -> { + Preconditions.checkState(drtConfig.getPrebookingParams().isPresent()); + + return new PersonBasedPrebookingLogic(drtConfig.getMode(), getter.getModal(PrebookingQueue.class), + getter.getModal(PopulationIteratorFactory.class), getter.get(TimeInterpretation.class), + prebookingSlack); + })); + addModalQSimComponentBinding().to(modalKey(PersonBasedPrebookingLogic.class)); + } + }; + } + + static public void install(Controler controller, DrtConfigGroup drtConfig, double prebookingSlack) { + controller.addOverridingQSimModule(createModule(drtConfig, prebookingSlack)); + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PrebookingLogic.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PrebookingLogic.java new file mode 100644 index 00000000000..d46edeea35f --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PrebookingLogic.java @@ -0,0 +1,11 @@ +package org.matsim.contrib.drt.prebooking.logic; + +/** + * An interface that tags a prebooking logic, but doesn't have any functionality + * itself. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public interface PrebookingLogic { + +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/ProbabilityBasedPrebookingLogic.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/ProbabilityBasedPrebookingLogic.java new file mode 100644 index 00000000000..2a62cb6b2d6 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/ProbabilityBasedPrebookingLogic.java @@ -0,0 +1,101 @@ +package org.matsim.contrib.drt.prebooking.logic; + +import java.util.Random; + +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator; +import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator.PopulationIteratorFactory; +import org.matsim.contrib.drt.prebooking.logic.helpers.PrebookingQueue; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; +import org.matsim.core.controler.Controler; +import org.matsim.core.mobsim.framework.events.MobsimInitializedEvent; +import org.matsim.core.mobsim.framework.listeners.MobsimInitializedListener; +import org.matsim.core.utils.timing.TimeInterpretation; +import org.matsim.core.utils.timing.TimeTracker; + +import com.google.common.base.Preconditions; + +/** + * This class represents a prebooking logic that searches for DRT legs in the + * population and decides based on a predefiend probability if each trip is + * prebooked or not. Furthermore, you can configure how much in advance to the + * planned departure time the request is submitted using the submissionSlack + * parameter. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class ProbabilityBasedPrebookingLogic implements PrebookingLogic, MobsimInitializedListener { + private final PrebookingQueue prebookingQueue; + private final PopulationIteratorFactory populationIteratorFactory; + private final TimeInterpretation timeInterpretation; + private final Random random; + + private final String mode; + private final double probability; + private final double submissionSlack; + + private ProbabilityBasedPrebookingLogic(String mode, PrebookingQueue prebookingQueue, + PopulationIteratorFactory populationIteratorFactory, TimeInterpretation timeInterpretation, Random random, + double probability, double submissionSlack) { + this.prebookingQueue = prebookingQueue; + this.populationIteratorFactory = populationIteratorFactory; + this.mode = mode; + this.timeInterpretation = timeInterpretation; + this.random = random; + this.probability = probability; + this.submissionSlack = submissionSlack; + } + + @Override + public void notifyMobsimInitialized(@SuppressWarnings("rawtypes") MobsimInitializedEvent e) { + PopulationIterator populationIterator = populationIteratorFactory.create(); + + while (populationIterator.hasNext()) { + var personItem = populationIterator.next(); + + TimeTracker timeTracker = new TimeTracker(timeInterpretation); + + for (PlanElement element : personItem.plan().getPlanElements()) { + if (element instanceof Leg) { + Leg leg = (Leg) element; + + if (mode.equals(leg.getMode()) && random.nextDouble() <= probability) { + double departureTime = timeTracker.getTime().seconds(); + double submissionTime = departureTime - submissionSlack; + + prebookingQueue.schedule(submissionTime, personItem.agent(), leg, departureTime); + } + } + + timeTracker.addElement(element); + } + } + + prebookingQueue.performInitialSubmissions(); + } + + static public AbstractDvrpModeQSimModule createModule(DrtConfigGroup drtConfig, double probability, + double submissionSlack) { + return new AbstractDvrpModeQSimModule(drtConfig.getMode()) { + @Override + protected void configureQSim() { + bindModal(ProbabilityBasedPrebookingLogic.class).toProvider(modalProvider(getter -> { + Preconditions.checkState(drtConfig.getPrebookingParams().isPresent()); + + return new ProbabilityBasedPrebookingLogic(drtConfig.getMode(), + getter.getModal(PrebookingQueue.class), getter.getModal(PopulationIteratorFactory.class), + getter.get(TimeInterpretation.class), new Random(getConfig().global().getRandomSeed()), + probability, submissionSlack); + })); + addModalQSimComponentBinding().to(modalKey(ProbabilityBasedPrebookingLogic.class)); + } + }; + } + + static public void install(Controler controller, DrtConfigGroup drtConfig, double probability, + double prebookingSlack) { + controller.addOverridingQSimModule(createModule(drtConfig, probability, prebookingSlack)); + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PopulationIterator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PopulationIterator.java new file mode 100644 index 00000000000..faf2c1ce87c --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PopulationIterator.java @@ -0,0 +1,57 @@ +package org.matsim.contrib.drt.prebooking.logic.helpers; + +import java.util.Iterator; + +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.contrib.drt.prebooking.logic.helpers.PopulationIterator.PersonItem; +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.qsim.QSim; +import org.matsim.core.mobsim.qsim.agents.HasModifiablePlan; + +/** + * This is a helper class that allows to loop through all persons that are + * active in a QSim. It is used to prebook drt legs for specific legs. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class PopulationIterator implements Iterator { + private final QSim qsim; + private final Iterator internalIterator; + + private PopulationIterator(Population population, QSim qsim) { + this.qsim = qsim; + this.internalIterator = population.getPersons().values().iterator(); + } + + @Override + public boolean hasNext() { + return internalIterator.hasNext(); + } + + @Override + public PersonItem next() { + Person person = internalIterator.next(); + MobsimAgent agent = qsim.getAgents().get(person.getId()); + Plan plan = ((HasModifiablePlan) agent).getModifiablePlan(); + return new PersonItem(agent, plan); + } + + public record PersonItem(MobsimAgent agent, Plan plan) { + } + + static public class PopulationIteratorFactory { + private final Population population; + private final QSim qsim; + + public PopulationIteratorFactory(Population population, QSim qsim) { + this.population = population; + this.qsim = qsim; + } + + public PopulationIterator create() { + return new PopulationIterator(population, qsim); + } + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PrebookingQueue.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PrebookingQueue.java new file mode 100644 index 00000000000..ed04764c9b4 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PrebookingQueue.java @@ -0,0 +1,81 @@ +package org.matsim.contrib.drt.prebooking.logic.helpers; + +import java.util.PriorityQueue; + +import org.matsim.api.core.v01.population.Leg; +import org.matsim.contrib.drt.prebooking.PrebookingManager; +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent; +import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener; + +import com.google.common.base.Preconditions; + +/** + * This service helps to define a PrebookingLogic where at some point in time + * (usually at the beginning of the simulaton), it is known in advance that a + * request will happen at a specific time. Once we know that the request will be + * sent, we can use the present class to put it in a queue, making sure the + * request will be *booked* the the time we want. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class PrebookingQueue implements MobsimBeforeSimStepListener { + private final PrebookingManager prebookingManager; + + private PriorityQueue queue = new PriorityQueue<>(); + private Integer sequence = 0; + + private double currentTime = Double.NEGATIVE_INFINITY; + + public PrebookingQueue(PrebookingManager prebookingManager) { + this.prebookingManager = prebookingManager; + } + + @Override + public void notifyMobsimBeforeSimStep(@SuppressWarnings("rawtypes") MobsimBeforeSimStepEvent event) { + performSubmissions(event.getSimulationTime()); + } + + private void performSubmissions(double time) { + currentTime = time; + + while (!queue.isEmpty() && queue.peek().submissionTime <= time) { + var item = queue.poll(); + prebookingManager.prebook(item.agent(), item.leg(), item.departuretime()); + } + } + + /** + * May be called by a submission logic to directly perform submission after the + * MobsimInitializedEvent, otherwise this is called automatically per time step + * to see if there are requests queued that need to be submitted. + */ + public void performInitialSubmissions() { + performSubmissions(Double.NEGATIVE_INFINITY); + } + + public void schedule(double submissionTime, MobsimAgent agent, Leg leg, double departureTime) { + Preconditions.checkArgument(submissionTime > currentTime, "Can only submit future requests"); + + synchronized (queue) { // synchronizing for queue and sequence + queue.add(new ScheduledSubmission(submissionTime, agent, leg, departureTime, sequence++)); + } + } + + private record ScheduledSubmission(double submissionTime, MobsimAgent agent, Leg leg, double departuretime, + int sequence) implements Comparable { + @Override + public int compareTo(ScheduledSubmission o) { + // sort by submissionTime, but otherwise keep the order of submission + int comparison = Double.compare(submissionTime, o.submissionTime); + + if (comparison != 0) { + return comparison; + } + + // comparing by sequence, because a PriorityQueue is *not* preserving the order + // of two elements with the same comparison value + return Integer.compare(sequence, o.sequence); + } + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java index 5b54e43eb45..03b9c551970 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java @@ -38,6 +38,7 @@ import org.matsim.contrib.drt.optimizer.insertion.selective.SelectiveInsertionSearchParams; import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingParams; import org.matsim.contrib.drt.optimizer.rebalancing.mincostflow.MinCostFlowRebalancingStrategyParams; +import org.matsim.contrib.drt.prebooking.PrebookingParams; import org.matsim.contrib.drt.speedup.DrtSpeedUpParams; import org.matsim.contrib.dvrp.router.DvrpModeRoutingNetworkModule; import org.matsim.contrib.dvrp.run.Modal; @@ -191,9 +192,6 @@ public enum OperationalScheme { @Comment("Store planned unshared drt route as a link sequence") public boolean storeUnsharedPath = false; // If true, the planned unshared path is stored and exported in plans - @PositiveOrZero - public double advanceRequestPlanningHorizon = 0; // beta-feature; planning horizon for advance (prebooked) requests - @NotNull private DrtInsertionSearchParams drtInsertionSearchParams; @@ -209,6 +207,9 @@ public enum OperationalScheme { @Nullable private DrtSpeedUpParams drtSpeedUpParams; + @Nullable + private PrebookingParams prebookingParams; + @Nullable private DrtRequestInsertionRetryParams drtRequestInsertionRetryParams; @@ -249,6 +250,11 @@ private void initSingletonParameterSets() { addDefinition(DrtRequestInsertionRetryParams.SET_NAME, DrtRequestInsertionRetryParams::new, () -> drtRequestInsertionRetryParams, params -> drtRequestInsertionRetryParams = (DrtRequestInsertionRetryParams)params); + + //prebooking (optional) + addDefinition(PrebookingParams.SET_NAME, PrebookingParams::new, + () -> prebookingParams, + params -> prebookingParams = (PrebookingParams)params); } @Override @@ -324,6 +330,10 @@ public Optional getDrtRequestInsertionRetryParam return Optional.ofNullable(drtRequestInsertionRetryParams); } + public Optional getPrebookingParams() { + return Optional.ofNullable(prebookingParams); + } + /** * Convenience method that brings syntax closer to syntax in, e.g., {@link RoutingConfigGroup} or {@link ScoringConfigGroup} */ diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeModule.java index fac873dbb8e..677e5504c3c 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeModule.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeModule.java @@ -24,8 +24,13 @@ import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector; import org.matsim.contrib.drt.fare.DrtFareHandler; import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingModule; +import org.matsim.contrib.drt.prebooking.analysis.PrebookingModeAnalysisModule; import org.matsim.contrib.drt.speedup.DrtSpeedUp; import org.matsim.contrib.drt.stops.DefaultStopTimeCalculator; +import org.matsim.contrib.drt.stops.MinimumStopDurationAdapter; +import org.matsim.contrib.drt.stops.PassengerStopDurationProvider; +import org.matsim.contrib.drt.stops.PrebookingStopTimeCalculator; +import org.matsim.contrib.drt.stops.StaticPassengerStopDurationProvider; import org.matsim.contrib.drt.stops.StopTimeCalculator; import org.matsim.contrib.dvrp.fleet.FleetModule; import org.matsim.contrib.dvrp.fleet.FleetSpecification; @@ -82,12 +87,27 @@ public void install() { getter.getModal(DrtEventSequenceCollector.class)))).asEagerSingleton(); addControlerListenerBinding().to(modalKey(DrtSpeedUp.class)); }); - + + bindModal(PassengerStopDurationProvider.class).toProvider(modalProvider(getter -> { + return StaticPassengerStopDurationProvider.of(drtCfg.stopDuration, 0.0); + })); + bindModal(DefaultStopTimeCalculator.class).toProvider(modalProvider(getter -> { return new DefaultStopTimeCalculator(drtCfg.stopDuration); })).in(Singleton.class); - - bindModal(StopTimeCalculator.class).to(modalKey(DefaultStopTimeCalculator.class)); + + if (drtCfg.getPrebookingParams().isEmpty()) { + bindModal(StopTimeCalculator.class).toProvider(modalProvider(getter -> { + return new DefaultStopTimeCalculator(drtCfg.stopDuration); + })).in(Singleton.class); + } else { + bindModal(StopTimeCalculator.class).toProvider(modalProvider(getter -> { + PassengerStopDurationProvider provider = getter.getModal(PassengerStopDurationProvider.class); + return new MinimumStopDurationAdapter(new PrebookingStopTimeCalculator(provider), drtCfg.stopDuration); + })); + + install(new PrebookingModeAnalysisModule(getMode())); + } install(new AdaptiveTravelTimeMatrixModule(drtCfg.mode)); } 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 ecaaf5b4065..b0dea305ac0 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 @@ -20,21 +20,29 @@ package org.matsim.contrib.drt.run; -import com.google.inject.Inject; -import com.google.inject.Provider; import org.matsim.contrib.drt.optimizer.DrtModeOptimizerQSimModule; import org.matsim.contrib.drt.passenger.DrtRequestCreator; +import org.matsim.contrib.drt.prebooking.PrebookingManager; +import org.matsim.contrib.drt.prebooking.PrebookingModeQSimModule; import org.matsim.contrib.drt.speedup.DrtSpeedUp; +import org.matsim.contrib.drt.vrpagent.DrtActionCreator; +import org.matsim.contrib.dvrp.passenger.AdvanceRequestProvider; import org.matsim.contrib.dvrp.passenger.DefaultPassengerRequestValidator; import org.matsim.contrib.dvrp.passenger.PassengerEngineQSimModule; +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.passenger.TeleportingPassengerEngine.TeleportedRouteCalculator; import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; import org.matsim.contrib.dvrp.vrpagent.VrpAgentSourceQSimModule; +import org.matsim.contrib.dvrp.vrpagent.VrpLegFactory; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + /** * @author Michal Maciejewski (michalm) */ @@ -68,6 +76,13 @@ protected void configureQSim() { install(optimizerQSimModule); } + if (drtCfg.getPrebookingParams().isPresent()) { + install(new PrebookingModeQSimModule(getMode())); + bindModal(AdvanceRequestProvider.class).to(modalKey(PrebookingManager.class)); + } else { + bindModal(AdvanceRequestProvider.class).toInstance(AdvanceRequestProvider.NONE); + } + bindModal(PassengerRequestValidator.class).to(DefaultPassengerRequestValidator.class).asEagerSingleton(); bindModal(PassengerRequestCreator.class).toProvider(new Provider() { @@ -79,5 +94,14 @@ public DrtRequestCreator get() { return new DrtRequestCreator(getMode(), events); } }).asEagerSingleton(); + + // this is not the actual selection which DynActionCreator is used, see + // DrtModeOptimizerQSimModule + bindModal(DrtActionCreator.class) + .toProvider(modalProvider(getter -> new DrtActionCreator(getter.getModal(PassengerHandler.class), + getter.getModal(VrpLegFactory.class)))) + .in(Singleton.class); // Not an eager binding, as taxi contrib doesn't need this implementation, but + // would search for VrpLegFactory which is provided in the + // DrtModeOptimizerModule if bound eagerly } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/DefaultRequestInsertionScheduler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/DefaultRequestInsertionScheduler.java index 5f40c9a80f6..32929a39984 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/DefaultRequestInsertionScheduler.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/DefaultRequestInsertionScheduler.java @@ -22,32 +22,29 @@ import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.DRIVE; import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STAY; -import java.util.Collections; import java.util.List; 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.optimizer.insertion.InsertionGenerator.Insertion; import org.matsim.contrib.drt.optimizer.insertion.InsertionWithDetourData; import org.matsim.contrib.drt.passenger.AcceptedDrtRequest; -import org.matsim.contrib.drt.schedule.*; 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.drt.stops.PassengerStopDurationProvider; import org.matsim.contrib.drt.stops.StopTimeCalculator; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.fleet.Fleet; 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.Schedule.ScheduleStatus; 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.schedule.Task.TaskStatus; import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker; import org.matsim.contrib.dvrp.util.LinkTimePair; import org.matsim.core.mobsim.framework.MobsimTimer; @@ -65,16 +62,18 @@ public class DefaultRequestInsertionScheduler implements RequestInsertionSchedul private final TravelTime travelTime; private final ScheduleTimingUpdater scheduleTimingUpdater; private final DrtTaskFactory taskFactory; - private StopTimeCalculator stopTimeCalculator; + private final StopTimeCalculator stopTimeCalculator; + private final boolean scheduleWaitBeforeDrive; public DefaultRequestInsertionScheduler(Fleet fleet, MobsimTimer timer, TravelTime travelTime, ScheduleTimingUpdater scheduleTimingUpdater, DrtTaskFactory taskFactory, - StopTimeCalculator stopTimeCalculator) { + StopTimeCalculator stopTimeCalculator, boolean scheduleWaitBeforeDrive) { this.timer = timer; this.travelTime = travelTime; this.scheduleTimingUpdater = scheduleTimingUpdater; this.taskFactory = taskFactory; this.stopTimeCalculator = stopTimeCalculator; + this.scheduleWaitBeforeDrive = scheduleWaitBeforeDrive; initSchedules(fleet); } @@ -97,6 +96,9 @@ public PickupDropoffTaskPair scheduleRequest(AcceptedDrtRequest request, Inserti dropoffTask.getBeginTime()); verifyTaskContinuity(insertion); + verifyConstraints(insertion); + verifyStructure(insertion.insertion.vehicleEntry.vehicle.getSchedule()); + return new PickupDropoffTaskPair(pickupTask, dropoffTask); } @@ -114,6 +116,50 @@ private void verifyTaskContinuity(InsertionWithDetourData insertion) { Verify.verify(first.getEndTime() == second.getBeginTime()); } } + + private final boolean verifyConstraints = false; + + private void verifyConstraints(InsertionWithDetourData insertion) { + /* + * Verifies that no request constraints are violated after scheduling. This + * check is only valid when simulating under free-flow conditions. + */ + if (verifyConstraints) { + Schedule schedule = insertion.insertion.vehicleEntry.vehicle.getSchedule(); + + for (Task task : schedule.getTasks()) { + if (task instanceof DrtStopTask) { + DrtStopTask stopTask = (DrtStopTask) task; + + for (AcceptedDrtRequest request : stopTask.getPickupRequests().values()) { + Verify.verify(stopTask.getEndTime() <= request.getLatestStartTime()); + } + + for (AcceptedDrtRequest request : stopTask.getDropoffRequests().values()) { + Verify.verify(stopTask.getBeginTime() <= request.getLatestArrivalTime()); + } + } + } + } + } + + private void verifyStructure(Schedule schedule) { + boolean previousDrive = false; + + int startIndex = schedule.getStatus().equals(ScheduleStatus.STARTED) ? schedule.getCurrentTask().getTaskIdx() + : 0; + + for (int index = startIndex; index < schedule.getTaskCount(); index++) { + Task task = schedule.getTasks().get(index); + + if (task instanceof DriveTask) { + Verify.verify(!previousDrive); + previousDrive = true; + } else { + previousDrive = false; + } + } + } private void verifyTimes(String messageStart, double timeFromInsertionData, double timeFromScheduler) { // The source of discrepancies is the first link travel time (as it should be taken into consideration @@ -145,26 +191,35 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour beforePickupTask = currentTask; VrpPathWithTravelData vrpPath = VrpPaths.createPath(vehicleEntry.start.link, request.getFromLink(), vehicleEntry.start.time, detourData.detourToPickup, travelTime); - ((OnlineDriveTaskTracker)beforePickupTask.getTaskTracker()).divertPath(vrpPath); + + if (vrpPath.getArrivalTime() < request.getEarliestStartTime() && scheduleWaitBeforeDrive) { + // prebooking case: we need to wait right now + ((OnlineDriveTaskTracker)beforePickupTask.getTaskTracker()).divertPath(VrpPaths.createZeroLengthPathForDiversion(diversion)); + beforePickupTask = insertDriveWithWait(vehicleEntry.vehicle, currentTask, vrpPath, request.getEarliestStartTime()); + } else { + ((OnlineDriveTaskTracker)beforePickupTask.getTaskTracker()).divertPath(vrpPath); + // prebooking: may want to wait after continuing to drive + beforePickupTask = insertWait(vehicleEntry.vehicle, beforePickupTask, request.getEarliestStartTime()); + } } else { // too late for diversion if (request.getFromLink() != vehicleEntry.start.link) { // add a new drive task VrpPathWithTravelData vrpPath = VrpPaths.createPath(vehicleEntry.start.link, request.getFromLink(), vehicleEntry.start.time, detourData.detourToPickup, travelTime); - beforePickupTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, DrtDriveTask.TYPE); - schedule.addTask(currentTask.getTaskIdx() + 1, beforePickupTask); - } else { // no need for a new drive task - beforePickupTask = currentTask; + // prebooking: may want to wait before or after driving + beforePickupTask = insertDriveWithWait(vehicleEntry.vehicle, currentTask, vrpPath, request.getEarliestStartTime()); + } else { // no need for a new drive task, but may want to wait until start of stop + beforePickupTask = insertWait(vehicleEntry.vehicle, currentTask, dropoffIdx); } } } else { // insert pickup after an existing stop/stay task - DrtStayTask stayTask = null; + StayTask stayTask = null; DrtStopTask stopTask = null; if (pickupIdx == 0) { if (scheduleStatus == ScheduleStatus.PLANNED) {// PLANNED schedule - stayTask = (DrtStayTask)schedule.getTasks().get(0); + stayTask = (StayTask)schedule.getTasks().get(0); stayTask.setEndTime(stayTask.getBeginTime());// could get later removed with ScheduleTimingUpdater } else if (STAY.isBaseTypeOf(currentTask)) { - stayTask = (DrtStayTask)currentTask; // ongoing stay task + stayTask = (StayTask)currentTask; // ongoing stay task if (stayTask.getEndTime() > now) { // stop stay task; a new stop/drive task can be inserted now stayTask.setEndTime(now); } @@ -174,8 +229,11 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour } else { stopTask = stops.get(pickupIdx - 1).task; // future stop task } - - if (stopTask != null && request.getFromLink() == stopTask.getLink()) { // no detour; no new stop task + + boolean canMergePickup = stopTask != null && request.getFromLink() == stopTask.getLink() + && stopTask.getEndTime() >= request.getEarliestStartTime(); + + if (canMergePickup) { // no detour; no new stop task // add pickup request to stop task stopTask.addPickupRequest(request); @@ -183,22 +241,12 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour stopTask.setEndTime(stopTimeCalculator.updateEndTimeForPickup(vehicleEntry.vehicle, stopTask, now, request.getRequest())); - /// ADDED - //// TODO this is copied, but has not been updated !!!!!!!!!!!!!!! // add drive from pickup if (pickupIdx == dropoffIdx) { // remove drive i->i+1 (if there is one) if (pickupIdx < stops.size()) {// there is at least one following stop DrtStopTask nextStopTask = stops.get(pickupIdx).task; - if (stopTask.getTaskIdx() + 2 != nextStopTask.getTaskIdx()) {// there must a drive task in - // between - throw new RuntimeException(); - } - if (stopTask.getTaskIdx() + 2 == nextStopTask.getTaskIdx()) {// there must a drive task in - // between - int driveTaskIdx = stopTask.getTaskIdx() + 1; - schedule.removeTask(schedule.getTasks().get(driveTaskIdx)); - } + removeBetween(schedule, stopTask, nextStopTask); } Link toLink = request.getToLink(); // pickup->dropoff @@ -206,7 +254,7 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour VrpPathWithTravelData vrpPath = VrpPaths.createPath(request.getFromLink(), toLink, stopTask.getEndTime(), detourData.detourFromPickup, travelTime); Task driveFromPickupTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, - DrtDriveTask.TYPE); + DrtDriveTask.TYPE); // immediate drive to dropoff schedule.addTask(stopTask.getTaskIdx() + 1, driveFromPickupTask); // update timings @@ -226,37 +274,29 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour // remove drive i->i+1 (if there is one) if (pickupIdx < stops.size()) {// there is at least one following stop DrtStopTask nextStopTask = stops.get(pickupIdx).task; - - // check: if there is at most one drive task in between - if (stayOrStopTask.getTaskIdx() + 2 != nextStopTask.getTaskIdx() // - && stayTask != null && stayTask.getTaskIdx() + 1 != nextStopTask.getTaskIdx()) { - throw new RuntimeException(); - } - if (stayOrStopTask.getTaskIdx() + 2 == nextStopTask.getTaskIdx()) { - // removing the drive task that is in between - int driveTaskIdx = stayOrStopTask.getTaskIdx() + 1; - schedule.removeTask(schedule.getTasks().get(driveTaskIdx)); - } + removeBetween(schedule, stayOrStopTask, nextStopTask); } - if (stayTask != null && request.getFromLink() == stayTask.getLink()) { + if (request.getFromLink() == stayOrStopTask.getLink()) { // the bus stays where it is - beforePickupTask = stayTask; + beforePickupTask = stayOrStopTask; + + // prebooking: but we may want to wait a bit if next stop is in a while + beforePickupTask = insertWait(vehicleEntry.vehicle, beforePickupTask, request.getEarliestStartTime()); } else {// add drive task to pickup location // insert drive i->pickup VrpPathWithTravelData vrpPath = VrpPaths.createPath(stayOrStopTask.getLink(), request.getFromLink(), stayOrStopTask.getEndTime(), detourData.detourToPickup, travelTime); - beforePickupTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, DrtDriveTask.TYPE); - schedule.addTask(stayOrStopTask.getTaskIdx() + 1, beforePickupTask); + // we may want to wait before or after driving + beforePickupTask = insertDriveWithWait(vehicleEntry.vehicle, stayOrStopTask, vrpPath, request.getEarliestStartTime()); } } } // insert pickup stop task - double startTime = beforePickupTask.getEndTime(); int taskIdx = beforePickupTask.getTaskIdx() + 1; - double stopEndTime = stopTimeCalculator.initEndTimeForPickup(vehicleEntry.vehicle, startTime, request.getRequest()); - DrtStopTask pickupStopTask = taskFactory.createStopTask(vehicleEntry.vehicle, startTime, + double stopEndTime = stopTimeCalculator.initEndTimeForPickup(vehicleEntry.vehicle, beforePickupTask.getEndTime(), request.getRequest()); + DrtStopTask pickupStopTask = taskFactory.createStopTask(vehicleEntry.vehicle, beforePickupTask.getEndTime(), stopEndTime, request.getFromLink()); schedule.addTask(taskIdx, pickupStopTask); pickupStopTask.addPickupRequest(request); @@ -264,16 +304,30 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour // add drive from pickup Link toLink = pickupIdx == dropoffIdx ? request.getToLink() // pickup->dropoff : stops.get(pickupIdx).task.getLink(); // pickup->i+1 + + double nextBeginTime = pickupIdx == dropoffIdx ? // + pickupStopTask.getEndTime() : // asap + stops.get(pickupIdx).task.getBeginTime(); // as planned + + if (request.getFromLink() == toLink) { + // prebooking case when we are already at the stop location, but next stop task happens in the future + Task afterPickupTask = insertWait(vehicleEntry.vehicle, pickupStopTask, nextBeginTime); + + scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle, afterPickupTask.getTaskIdx() + 1, + afterPickupTask.getEndTime()); + } else { + VrpPathWithTravelData vrpPath = VrpPaths.createPath(request.getFromLink(), toLink, pickupStopTask.getEndTime(), + detourData.detourFromPickup, travelTime); + + // may want to wait now or before next stop task + Task afterPickupTask = insertDriveWithWait(vehicleEntry.vehicle, pickupStopTask, vrpPath, nextBeginTime); - VrpPathWithTravelData vrpPath = VrpPaths.createPath(request.getFromLink(), toLink, pickupStopTask.getEndTime(), - detourData.detourFromPickup, travelTime); - Task driveFromPickupTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, DrtDriveTask.TYPE); - schedule.addTask(taskIdx + 1, driveFromPickupTask); - - // update timings - // TODO should be enough to update the timeline only till dropoffIdx... - scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle, taskIdx + 2, - driveFromPickupTask.getEndTime()); + // update timings + // TODO should be enough to update the timeline only till dropoffIdx... + scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle, afterPickupTask.getTaskIdx() + 1, + afterPickupTask.getEndTime()); + } + return pickupStopTask; } @@ -288,7 +342,7 @@ private DrtStopTask insertDropoff(AcceptedDrtRequest request, InsertionWithDetou int dropoffIdx = insertion.dropoff.index; var detourData = insertionWithDetourData.detourData; - Task driveToDropoffTask; + final Task driveToDropoffTask; if (pickupIdx == dropoffIdx) { // no drive to dropoff int pickupTaskIdx = pickupTask.getTaskIdx(); driveToDropoffTask = schedule.getTasks().get(pickupTaskIdx + 1); @@ -310,16 +364,13 @@ private DrtStopTask insertDropoff(AcceptedDrtRequest request, InsertionWithDetou // remove drive j->j+1 (if j is not the last stop) if (dropoffIdx < stops.size()) { DrtStopTask nextStopTask = stops.get(dropoffIdx).task; - if (stopTask.getTaskIdx() + 2 != nextStopTask.getTaskIdx()) { - throw new IllegalStateException(); - } - int driveTaskIdx = stopTask.getTaskIdx() + 1; - schedule.removeTask(schedule.getTasks().get(driveTaskIdx)); + removeBetween(schedule, stopTask, nextStopTask); } // insert drive i->dropoff VrpPathWithTravelData vrpPath = VrpPaths.createPath(stopTask.getLink(), request.getToLink(), stopTask.getEndTime(), detourData.detourToDropoff, travelTime); + // direct drive to dropoff (no waiting) driveToDropoffTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, DrtDriveTask.TYPE); schedule.addTask(stopTask.getTaskIdx() + 1, driveToDropoffTask); } @@ -353,13 +404,107 @@ private DrtStopTask insertDropoff(AcceptedDrtRequest request, InsertionWithDetou VrpPathWithTravelData vrpPath = VrpPaths.createPath(request.getToLink(), toLink, dropoffStopTask.getEndTime(), detourData.detourFromDropoff, travelTime); - Task driveFromDropoffTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, DrtDriveTask.TYPE); - schedule.addTask(taskIdx + 1, driveFromDropoffTask); + + if (toLink == request.getToLink()) { + // prebooking case: we stay, but may add some wait time until the next stop + Task afterDropoffTask = insertWait(vehicleEntry.vehicle, dropoffStopTask, + stops.get(dropoffIdx).task.getBeginTime()); - // update timings - scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle, taskIdx + 2, - driveFromDropoffTask.getEndTime()); + scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle, + afterDropoffTask.getTaskIdx() + 1, afterDropoffTask.getEndTime()); + } else { + // may want to wait here or after driving before starting next stop + Task afterDropoffTask = insertDriveWithWait(vehicleEntry.vehicle, dropoffStopTask, vrpPath, + stops.get(dropoffIdx).task.getBeginTime()); + + scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle, + afterDropoffTask.getTaskIdx() + 1, afterDropoffTask.getEndTime()); + } } return dropoffStopTask; } + + /** + * Removes drive and wait tasks between the startTask and the endTask + */ + private void removeBetween(Schedule schedule, Task startTask, Task endTask) { + Verify.verify(endTask.getTaskIdx() >= startTask.getTaskIdx()); + Verify.verify(endTask.getTaskIdx() <= startTask.getTaskIdx() + 3); + + int waitCount = 0; + int driveCount = 0; + + int removeCount = endTask.getTaskIdx() - startTask.getTaskIdx() - 1; + + for (int k = 0; k < removeCount; k++) { + Task task = schedule.getTasks().get(startTask.getTaskIdx() + 1); + + if (DrtTaskBaseType.DRIVE.isBaseTypeOf(task.getTaskType())) { + driveCount++; + } else if (DrtStayTask.TYPE.equals(task.getTaskType())) { + waitCount++; + } else { + throw new IllegalStateException("Invalid schedule structure: expected WAIT or DRIVE task"); + } + + schedule.removeTask(task); + } + + Verify.verify(waitCount <= 1); + Verify.verify(driveCount <= 1); + } + + 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; + } + + 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; + } + } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/vrpagent/DrtActionCreator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/vrpagent/DrtActionCreator.java index 1d57080e521..bb87604c123 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/vrpagent/DrtActionCreator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/vrpagent/DrtActionCreator.java @@ -25,15 +25,12 @@ import org.matsim.contrib.drt.schedule.DrtStopTask; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.passenger.PassengerHandler; -import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.dvrp.schedule.Task; -import org.matsim.contrib.dvrp.tracker.OnlineTrackerListener; import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic; import org.matsim.contrib.dvrp.vrpagent.VrpLegFactory; import org.matsim.contrib.dynagent.DynAction; import org.matsim.contrib.dynagent.DynAgent; import org.matsim.contrib.dynagent.IdleDynActivity; -import org.matsim.core.mobsim.framework.MobsimTimer; /** * @author michalm @@ -44,11 +41,6 @@ public class DrtActionCreator implements VrpAgentLogic.DynActionCreator { private final PassengerHandler passengerHandler; private final VrpLegFactory legFactory; - public DrtActionCreator(PassengerHandler passengerHandler, MobsimTimer timer, DvrpConfigGroup dvrpCfg) { - this(passengerHandler, v -> VrpLegFactory.createWithOnlineTracker(dvrpCfg.mobsimMode, v, - OnlineTrackerListener.NO_LISTENER, timer)); - } - public DrtActionCreator(PassengerHandler passengerHandler, VrpLegFactory legFactory) { this.passengerHandler = passengerHandler; this.legFactory = legFactory; diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/fare/DrtFareHandlerTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/fare/DrtFareHandlerTest.java index 490eea446df..2751caf417e 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/fare/DrtFareHandlerTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/fare/DrtFareHandlerTest.java @@ -70,7 +70,7 @@ public void reset(int iteration) { { var requestId = Id.create(0, Request.class); events.processEvent(new DrtRequestSubmittedEvent(0.0, mode, requestId, personId, Id.createLinkId("12"), - Id.createLinkId("23"), 240, 1000, 0.0, 0.0)); + Id.createLinkId("23"), 240, 1000, 0.0, 0.0, 0.0)); events.processEvent(new PassengerDroppedOffEvent(300.0, mode, requestId, personId, null)); events.flush(); @@ -81,7 +81,7 @@ public void reset(int iteration) { // test minFarePerTrip var requestId = Id.create(1, Request.class); events.processEvent(new DrtRequestSubmittedEvent(0.0, mode, requestId, personId, Id.createLinkId("45"), - Id.createLinkId("56"), 24, 100, 0.0, 0.0)); + Id.createLinkId("56"), 24, 100, 0.0, 0.0, 0.0)); events.processEvent(new PassengerDroppedOffEvent(300.0, mode, requestId, personId, null)); events.finishProcessing(); diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImplTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImplTest.java index 79bde9d12c8..035367e15fc 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImplTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImplTest.java @@ -22,11 +22,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.matsim.contrib.drt.optimizer.VehicleDataEntryFactoryImpl.computeSlackTimes; -import static org.matsim.contrib.drt.optimizer.Waypoint.Stop; + +import java.util.Arrays; +import java.util.List; import org.junit.Test; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; +import org.matsim.contrib.drt.optimizer.Waypoint.Stop; import org.matsim.contrib.drt.schedule.DefaultDrtStopTask; import org.matsim.contrib.drt.schedule.DrtStayTask; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; @@ -48,47 +51,76 @@ public class VehicleDataEntryFactoryImplTest { @Test public void computeSlackTimes_withStops() { + final List precedingStayTimes = Arrays.asList(0.0, 0.0); + //final stay task not started - vehicle slack time is 50 - assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] { stop0, stop1 }, null)).containsExactly(20, 20, 30, 50); + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] { stop0, stop1 }, null, precedingStayTimes)).containsExactly(20, 20, 30, 50); //final stay task not started - vehicle slack time is 25 and limits the slack times at stop1 - assertThat(computeSlackTimes(vehicle(500, 475), 100, new Stop[] { stop0, stop1 }, null)).containsExactly(20, 20, 25, 25); + assertThat(computeSlackTimes(vehicle(500, 475), 100, new Stop[] { stop0, stop1 }, null, precedingStayTimes)).containsExactly(20, 20, 25, 25); //final stay task not started - vehicle slack time is 10 and limits the slack times at all stops - assertThat(computeSlackTimes(vehicle(500, 490), 100, new Stop[] { stop0, stop1 }, null)).containsExactly(10, 10, 10, 10); + assertThat(computeSlackTimes(vehicle(500, 490), 100, new Stop[] { stop0, stop1 }, null, precedingStayTimes)).containsExactly(10, 10, 10, 10); } @Test public void computeSlackTimes_withoutStops() { + final List precedingStayTimes = Arrays.asList(); + //final stay task not started yet - vehicle slack time is 10 - assertThat(computeSlackTimes(vehicle(500, 490), 485, new Stop[] {}, null)).containsExactly(10, 10); + assertThat(computeSlackTimes(vehicle(500, 490), 485, new Stop[] {}, null, precedingStayTimes)).containsExactly(10, 10); //final stay task just started - vehicle slack time is 10 - assertThat(computeSlackTimes(vehicle(500, 490), 490, new Stop[] {}, null)).containsExactly(10, 10); + assertThat(computeSlackTimes(vehicle(500, 490), 490, new Stop[] {}, null, precedingStayTimes)).containsExactly(10, 10); //final stay task half completed - vehicle slack time is 5 - assertThat(computeSlackTimes(vehicle(500, 490), 495, new Stop[] {}, null)).containsExactly(5, 5); + assertThat(computeSlackTimes(vehicle(500, 490), 495, new Stop[] {}, null, precedingStayTimes)).containsExactly(5, 5); //final stay task just completed - vehicle slack time is 0 - assertThat(computeSlackTimes(vehicle(500, 490), 500, new Stop[] {}, null)).containsExactly(0, 0); + assertThat(computeSlackTimes(vehicle(500, 490), 500, new Stop[] {}, null, precedingStayTimes)).containsExactly(0, 0); //final stay task started, but delayed - vehicle slack time is 0 - assertThat(computeSlackTimes(vehicle(500, 510), 510, new Stop[] {}, null)).containsExactly(0, 0); + assertThat(computeSlackTimes(vehicle(500, 510), 510, new Stop[] {}, null, precedingStayTimes)).containsExactly(0, 0); //final stay task planned after vehicle end time - vehicle slack time is 0s - assertThat(computeSlackTimes(vehicle(500, 510), 300, new Stop[] {}, null)).containsExactly(0, 0); + assertThat(computeSlackTimes(vehicle(500, 510), 300, new Stop[] {}, null, precedingStayTimes)).containsExactly(0, 0); } @Test public void computeSlackTimes_withStart() { + final List noPrecedingStayTimes = Arrays.asList(); + final List onePrecedingStayTime = Arrays.asList(0.0); + //start without stop - assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] {}, stop0)).containsExactly(30, 50); + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] {}, stop0, noPrecedingStayTimes)).containsExactly(30, 50); //start without stop - assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] {}, stop1)).containsExactly(30, 50); + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] {}, stop1, noPrecedingStayTimes)).containsExactly(30, 50); //start with stop - assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] { stop1 }, stop0)).containsExactly(30, 30, 50); + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] { stop1 }, stop0, onePrecedingStayTime)).containsExactly(30, 30, 50); + } + + @Test + public void computeSlackTimes_withPrecedingStayTimes() { + final List precedingStayTimes = Arrays.asList( // + 0.0, // + 33.0 // second stop is a prebooked pickup, so slack for insertion after first stop is longer + ); + + // note that these examples are naively adapted from computeSlackTimes_withStops + // in practice the slack would never pass the service end time slack (ie the + // last value in the list) if the preceding insertions were done correctly and + // there was no congestion + + //final stay task not started - vehicle slack time is 50 + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] { stop0, stop1 }, null, precedingStayTimes)).containsExactly(20, 20, 63, 50); + + //final stay task not started - vehicle slack time is 25 and limits the slack times at stop1 + assertThat(computeSlackTimes(vehicle(500, 475), 100, new Stop[] { stop0, stop1 }, null, precedingStayTimes)).containsExactly(20, 20, 58, 25); + + //final stay task not started - vehicle slack time is 10 and limits the slack times at all stops + assertThat(computeSlackTimes(vehicle(500, 490), 100, new Stop[] { stop0, stop1 }, null, precedingStayTimes)).containsExactly(20, 20, 43, 10); } private Stop stop(double beginTime, double latestArrivalTime, double endTime, double latestDepartureTime) { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/BestInsertionFinderTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/BestInsertionFinderTest.java index 14b8c359ec5..32b44c82f2f 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/BestInsertionFinderTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/BestInsertionFinderTest.java @@ -132,7 +132,7 @@ private void whenInsertionThenCost(InsertionWithDetourData insertion, double cos private InsertionWithDetourData insertion(String vehicleId, int pickupIdx, int dropoffIdx) { var vehicle = mock(DvrpVehicle.class); when(vehicle.getId()).thenReturn(Id.create(vehicleId, DvrpVehicle.class)); - var vehicleEntry = new VehicleEntry(vehicle, null, null, null, 0); + var vehicleEntry = new VehicleEntry(vehicle, null, null, null, null, 0); var pickupInsertion = new InsertionGenerator.InsertionPoint(pickupIdx, null, null, null); var dropoffInsertion = new InsertionGenerator.InsertionPoint(dropoffIdx, null, null, null); diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java index dcdc662f335..ee1e2efc29a 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java @@ -23,7 +23,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.matsim.contrib.drt.optimizer.insertion.DefaultUnplannedRequestInserter.NO_INSERTION_FOUND_CAUSE; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; @@ -45,6 +49,7 @@ import org.matsim.contrib.drt.schedule.DefaultDrtStopTask; import org.matsim.contrib.drt.scheduler.RequestInsertionScheduler; import org.matsim.contrib.drt.scheduler.RequestInsertionScheduler.PickupDropoffTaskPair; +import org.matsim.contrib.drt.stops.StaticPassengerStopDurationProvider; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.fleet.Fleet; import org.matsim.contrib.dvrp.optimizer.Request; @@ -197,7 +202,7 @@ public void acceptedRequest() { var unplannedRequests = requests(request1); double now = 15; - var vehicle1Entry = new VehicleEntry(vehicle1, null, null, null, 0); + var vehicle1Entry = new VehicleEntry(vehicle1, null, null, null, null, 0); var createEntryCounter = new MutableInt(); VehicleEntry.EntryFactory entryFactory = (vehicle, currentTime) -> { //make sure the right arguments are passed @@ -283,7 +288,7 @@ private DefaultUnplannedRequestInserter newInserter(Fleet fleet, double now, DrtInsertionSearch insertionSearch, RequestInsertionScheduler insertionScheduler) { return new DefaultUnplannedRequestInserter(mode, fleet, () -> now, eventsManager, insertionScheduler, vehicleEntryFactory, insertionRetryQueue, insertionSearch, DrtOfferAcceptor.DEFAULT_ACCEPTOR, - rule.forkJoinPool); + rule.forkJoinPool, StaticPassengerStopDurationProvider.of(10.0, 0.0)); } private Link link(String id) { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java index f9c25dd60cf..563788c0f5c 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java @@ -71,7 +71,7 @@ private void assertCalculate(Insertion insertion, DetourTimeInfo detourTimeInfo, } private VehicleEntry entry(double[] slackTimes) { - return new VehicleEntry(null, null, null, slackTimes, 0); + return new VehicleEntry(null, null, null, slackTimes, null, 0); } private Link link(String id) { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorTest.java index 16e708701fd..3489c5ee854 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorTest.java @@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collections; import java.util.List; import org.junit.Test; @@ -36,7 +37,6 @@ import org.matsim.contrib.drt.optimizer.insertion.InsertionWithDetourData.InsertionDetourData; import org.matsim.contrib.drt.passenger.DrtRequest; import org.matsim.contrib.drt.schedule.DefaultDrtStopTask; -import org.matsim.contrib.drt.stops.DefaultPassengerStopDurationProvider; import org.matsim.contrib.drt.stops.DefaultStopTimeCalculator; import org.matsim.contrib.dvrp.path.OneToManyPathSearch.PathData; import org.matsim.contrib.dvrp.schedule.Task; @@ -234,7 +234,8 @@ private Waypoint.Stop stop(double beginTime, Link link) { } private VehicleEntry entry(Waypoint.Start start, Waypoint.Stop... stops) { - return new VehicleEntry(null, start, ImmutableList.copyOf(stops), null, 0); + List precedingStayTimes = Collections.nCopies(stops.length, 0.0); + return new VehicleEntry(null, start, ImmutableList.copyOf(stops), null, precedingStayTimes, 0); } private InsertionDetourData detourData(double toPickupTT, double fromPickupTT, double toDropoffTT, diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorWithVariableDurationTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorWithVariableDurationTest.java index 8f9b037a7dc..461a598d874 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorWithVariableDurationTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorWithVariableDurationTest.java @@ -20,8 +20,11 @@ package org.matsim.contrib.drt.optimizer.insertion; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableTable; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.List; + import org.junit.Test; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; @@ -42,9 +45,8 @@ import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.testcases.fakes.FakeLink; -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableTable; /** * @author Michal Maciejewski (michalm) @@ -274,7 +276,8 @@ private Waypoint.Stop stop(double beginTime, Link link) { } private VehicleEntry entry(Waypoint.Start start, Waypoint.Stop... stops) { - return new VehicleEntry(null, start, ImmutableList.copyOf(stops), null, 0); + List precedingStayTimes = Collections.nCopies(stops.length, 0.0); + return new VehicleEntry(null, start, ImmutableList.copyOf(stops), null, precedingStayTimes, 0); } private InsertionWithDetourData insertion(VehicleEntry entry, int pickupIdx, int dropoffIdx, diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java index 4509e5f90ad..c433ced72db 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.Test; @@ -37,7 +38,6 @@ import org.matsim.contrib.drt.optimizer.insertion.InsertionGenerator.Insertion; import org.matsim.contrib.drt.passenger.DrtRequest; import org.matsim.contrib.drt.schedule.DefaultDrtStopTask; -import org.matsim.contrib.drt.stops.DefaultPassengerStopDurationProvider; import org.matsim.contrib.drt.stops.DefaultStopTimeCalculator; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.fleet.DvrpVehicleImpl; @@ -65,6 +65,7 @@ public class InsertionGeneratorTest { private final Link fromLink = link("from"); private final Link toLink = link("to"); private final DrtRequest drtRequest = DrtRequest.newBuilder().fromLink(fromLink).toLink(toLink).build(); + private final DrtRequest prebookedRequest = DrtRequest.newBuilder().fromLink(fromLink).toLink(toLink).earliestStartTime(100).build(); private final Link depotLink = link("depot"); private final DvrpVehicleSpecification vehicleSpecification = ImmutableDvrpVehicleSpecification.newBuilder() @@ -210,7 +211,10 @@ public void startEmpty_twoStops_notFullBetweenStops_tightSlackTimes() { double[] slackTimes = { 0, 0, // impossible insertions: 00, 01, 02 (pickup at 0 is not possible) 500, // additional impossible insertions: 11 (too long total detour); however 12 is possible 1000 }; // 22 is possible - VehicleEntry entry = new VehicleEntry(vehicle, start, ImmutableList.of(stop0, stop1), slackTimes, 0); + + List precedingStayTimes = Arrays.asList(0.0, 0.0); + + VehicleEntry entry = new VehicleEntry(vehicle, start, ImmutableList.of(stop0, stop1), slackTimes, precedingStayTimes, 0); var insertions = new ArrayList(); {//12 @@ -344,6 +348,52 @@ public void noDetourForDropoff_vehicleOutgoingFullAfterDropoff_insertionPossible //pickup after stop 0 new Insertion(drtRequest, entry, 2, 2)); } + + @Test + public void startEmpty_prebookedRequest() { + Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0); + VehicleEntry entry = entry(start); + assertInsertionsOnly(prebookedRequest, entry, + new Insertion(prebookedRequest, entry, 0, 0)); + } + + @Test + public void startEmpty_onlineRequest_beforeAlreadyPrebookedOtherRequest() { + Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0); + Waypoint.Stop stop0 = stop(200, fromLink, 1); + Waypoint.Stop stop1 = stop(400, link("stop"), 0); + VehicleEntry entry = entry(start, stop0, stop1); + assertInsertionsOnly(drtRequest, entry, + new Insertion(drtRequest, entry, 0, 0), + new Insertion(drtRequest, entry, 0, 1), + new Insertion(drtRequest, entry, 0, 2), + new Insertion(drtRequest, entry, 1, 1), + new Insertion(drtRequest, entry, 1, 2), + new Insertion(drtRequest, entry, 2, 2) + ); + } + + @Test + public void startEmpty_prebookedRequest_inMiddleOfAlreadyPrebookedOtherRequest() { + Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0); + Waypoint.Stop stop0 = stop(50, fromLink, 1); + Waypoint.Stop stop1 = stop(300, link("stop"), 0); + VehicleEntry entry = entry(start, stop0, stop1); + assertInsertionsOnly(prebookedRequest, entry, + new Insertion(prebookedRequest, entry, 1, 1), + new Insertion(prebookedRequest, entry, 1, 2), + new Insertion(prebookedRequest, entry, 2, 2)); + } + + @Test + public void startEmpty_prebookedRequest_afterAlreadyPrebookedOtherRequest() { + Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0); + Waypoint.Stop stop0 = stop(20, fromLink, 1); + Waypoint.Stop stop1 = stop(70, link("stop"), 0); + VehicleEntry entry = entry(start, stop0, stop1); + assertInsertionsOnly(prebookedRequest, entry, + new Insertion(prebookedRequest, entry, 2, 2)); + } private Link link(String id) { return new FakeLink(Id.createLinkId(id)); @@ -401,6 +451,7 @@ private Waypoint.Stop stop(double beginTime, Link link, int outgoingOccupancy) { private VehicleEntry entry(Waypoint.Start start, Waypoint.Stop... stops) { var slackTimes = new double[stops.length + 2]; Arrays.fill(slackTimes, Double.POSITIVE_INFINITY); - return new VehicleEntry(vehicle, start, ImmutableList.copyOf(stops), slackTimes, 0); + List precedingStayTimes = Collections.nCopies(stops.length, 0.0); + return new VehicleEntry(vehicle, start, ImmutableList.copyOf(stops), slackTimes, precedingStayTimes, 0); } } diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/DetourPathDataCacheTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/DetourPathDataCacheTest.java index 283f69b0cf3..af7c0c331f2 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/DetourPathDataCacheTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/DetourPathDataCacheTest.java @@ -132,7 +132,7 @@ private Link link(String id) { private VehicleEntry entry(Link startLink, Link... stopLinks) { return new VehicleEntry(null, new Waypoint.Start(null, startLink, 0, 0), - Arrays.stream(stopLinks).map(this::stop).collect(ImmutableList.toImmutableList()), null, 0); + Arrays.stream(stopLinks).map(this::stop).collect(ImmutableList.toImmutableList()), null, null, 0); } private Waypoint.Stop stop(Link link) { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/KNearestInsertionsAtEndFilterTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/KNearestInsertionsAtEndFilterTest.java index e971c5a312e..77fc80cf388 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/KNearestInsertionsAtEndFilterTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/KNearestInsertionsAtEndFilterTest.java @@ -124,7 +124,7 @@ private Waypoint.Stop stop(double endTime) { private VehicleEntry vehicleEntry(String id, Waypoint.Start start, Waypoint.Stop... stops) { var vehicle = mock(DvrpVehicle.class); when(vehicle.getId()).thenReturn(Id.create(id, DvrpVehicle.class)); - return new VehicleEntry(vehicle, start, ImmutableList.copyOf(stops), null, 0); + return new VehicleEntry(vehicle, start, ImmutableList.copyOf(stops), null, null, 0); } private List filterOneInsertionAtEnd(InsertionWithDetourData... insertions) { 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 new file mode 100644 index 00000000000..63e4e1bae45 --- /dev/null +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java @@ -0,0 +1,473 @@ +package org.matsim.contrib.drt.prebooking; + +import static org.junit.Assert.assertEquals; + +import org.junit.Rule; +import org.junit.Test; +import org.matsim.contrib.drt.prebooking.PrebookingTestEnvironment.RequestInfo; +import org.matsim.contrib.drt.prebooking.logic.AttributeBasedPrebookingLogic; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.core.controler.Controler; +import org.matsim.testcases.MatsimTestUtils; + +/** + * @author Sebastian Hörl (sebhoerl) / IRT SystemX + */ +public class PrebookingTest { + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public void withoutPrebookedRequests() { + /*- + * Standard test running with prebooking but without any prebooked requests + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("personA", 0, 0, 5, 5, 2000.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + controller.run(); + + RequestInfo requestInfo = environment.getRequestInfo().get("personA"); + assertEquals(2000.0, requestInfo.submissionTime, 1e-3); + assertEquals(2146.0, requestInfo.pickupTime, 1e-3); + assertEquals(2357.0, requestInfo.dropoffTime, 1e-3); + + var taskInfo = environment.getTaskInfo().get("vehicleA"); + assertEquals("STAY", taskInfo.get(0).type); + assertEquals("DRIVE", taskInfo.get(1).type); + assertEquals("STOP", taskInfo.get(2).type); + assertEquals("DRIVE", taskInfo.get(3).type); + assertEquals("STOP", taskInfo.get(4).type); + + assertEquals(2001.0, taskInfo.get(1).startTime, 1e-3); + + assertEquals(2086.0, taskInfo.get(2).startTime, 1e-3); + assertEquals(2146.0, taskInfo.get(2).endTime, 1e-3); + } + + private void installPrebooking(Controler controller) { + DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(controller.getConfig()); + drtConfig.addParameterSet(new PrebookingParams()); + AttributeBasedPrebookingLogic.install(controller, drtConfig); + } + + @Test + public void oneRequestArrivingLate() { + /*- + * One request arriving after the requested departure time. Vehicle should wait + * and depart with appropriate delay (taking into account a fixed duration for + * the person to enter instead of a fixed stop duration). + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + // 1800 indicated but only departing 2000 + .addRequest("personA", 0, 0, 5, 5, 2000.0, 0.0, 2000.0 - 200.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + RequestInfo requestInfo = environment.getRequestInfo().get("personA"); + assertEquals(0.0, requestInfo.submissionTime, 1e-3); + assertEquals(2060.0, requestInfo.pickupTime, 1e-3); + assertEquals(2271.0, requestInfo.dropoffTime, 1e-3); + + var taskInfo = environment.getTaskInfo().get("vehicleA"); + assertEquals("STAY", taskInfo.get(0).type); + assertEquals("DRIVE", taskInfo.get(1).type); + assertEquals("STAY", taskInfo.get(2).type); + assertEquals("STOP", taskInfo.get(3).type); + assertEquals("DRIVE", taskInfo.get(4).type); + assertEquals("STOP", taskInfo.get(5).type); + + assertEquals(1.0, taskInfo.get(1).startTime, 1e-3); // Pickup drive + assertEquals(86.0, taskInfo.get(2).startTime, 1e-3); // Starting to wait + assertEquals(1800.0, taskInfo.get(3).startTime, 1e-3); // Starting stop + assertEquals(2060.0, taskInfo.get(3).endTime, 1e-3); // Ending stop (260s duration) + assertEquals(2060.0, taskInfo.get(4).startTime, 1e-3); // Starting drive (ending stop) + } + + @Test + public void oneRequestArrivingEarly() { + /*- + * One request arriving in advance before the requested departure time. Vehicle + * will pickup up agent and then depart after the duration to enter the vehicle. + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + // 2200 indicated but already departing 2000 + .addRequest("personA", 0, 0, 5, 5, 2000.0, 0.0, 2000.0 + 200.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + RequestInfo requestInfo = environment.getRequestInfo().get("personA"); + assertEquals(0.0, requestInfo.submissionTime, 1e-3); + assertEquals(2260.0 + 1.0, requestInfo.pickupTime, 1e-3); // One second for notifying vehicle + assertEquals(2472.0, requestInfo.dropoffTime, 1e-3); + + var taskInfo = environment.getTaskInfo().get("vehicleA"); + assertEquals("STAY", taskInfo.get(0).type); + assertEquals("DRIVE", taskInfo.get(1).type); + assertEquals("STAY", taskInfo.get(2).type); + assertEquals("STOP", taskInfo.get(3).type); + assertEquals("DRIVE", taskInfo.get(4).type); + assertEquals("STOP", taskInfo.get(5).type); + + assertEquals(1.0, taskInfo.get(1).startTime, 1e-3); // Pickup drive + assertEquals(86.0, taskInfo.get(2).startTime, 1e-3); // Starting to wait + assertEquals(2200.0, taskInfo.get(3).startTime, 1e-3); // Starting stop + assertEquals(2261.0, taskInfo.get(3).endTime, 1e-3); // Ending stop (60s) + assertEquals(2261.0, taskInfo.get(4).startTime, 1e-3); // Starting drive (ending stop) + } + + @Test + public void twoSequentialRequests() { + /*- + * Two requests that are scheduled in advance. + * - First the early one is submitted, then the late one + * - First the early one is picked up and dropped off, then the late one. + */ + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("earlyRequest", 0, 0, 5, 5, 2000.0, 0.0) // + .addRequest("lateRequest", 2, 2, 3, 3, 4000.0, 1000.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + { + RequestInfo requestInfo = environment.getRequestInfo().get("earlyRequest"); + assertEquals(0.0, requestInfo.submissionTime, 1e-3); + assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2272.0, requestInfo.dropoffTime, 1e-3); + } + + { + RequestInfo requestInfo = environment.getRequestInfo().get("lateRequest"); + assertEquals(1000.0, requestInfo.submissionTime, 1e-3); + assertEquals(4000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(4104.0, requestInfo.dropoffTime, 1e-3); + } + } + + @Test + public void twoSequentialRequests_inverseSubmission() { + /*- + * Two requests that are scheduled in advance. + * - First the late one is submitted, then the early one (inverse of above test). + * - First the early one is picked up and dropped off, then the late one. + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("earlyRequest", 0, 0, 5, 5, 2000.0, 1000.0) // + .addRequest("lateRequest", 2, 2, 3, 3, 4000.0, 0.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + { + RequestInfo requestInfo = environment.getRequestInfo().get("earlyRequest"); + assertEquals(1000.0, requestInfo.submissionTime, 1e-3); + assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2272.0, requestInfo.dropoffTime, 1e-3); + } + + { + RequestInfo requestInfo = environment.getRequestInfo().get("lateRequest"); + assertEquals(0.0, requestInfo.submissionTime, 1e-3); + assertEquals(4000.0 + 60.0, requestInfo.pickupTime, 1e-3); + assertEquals(4103.0, requestInfo.dropoffTime, 1e-3); + } + } + + @Test + public void sameTrip_differentDepartureTime() { + /*- + * Two requests with the same origin and destination, but distinct departure time. + * - First, early one is submitted, then late one. + * - Vehicle picks up and drops off early one, then late one. + * - In total four stops (P,D,P,D) + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("earlyRequest", 1, 1, 5, 5, 2000.0, 1000.0) // + .addRequest("lateRequest", 1, 1, 5, 5, 4000.0, 1100.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + { + RequestInfo requestInfo = environment.getRequestInfo().get("earlyRequest"); + assertEquals(1000.0, requestInfo.submissionTime, 1e-3); + assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2230.0, requestInfo.dropoffTime, 1e-3); + } + + { + RequestInfo requestInfo = environment.getRequestInfo().get("lateRequest"); + assertEquals(1100.0, requestInfo.submissionTime, 1e-3); + assertEquals(4000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(4230.0, requestInfo.dropoffTime, 1e-3); + } + + // Four stops, 2x pickup, 2x dropoff + assertEquals(4, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count()); + } + + @Test + public void sameTrip_sameDepartureTime() { + /*- + * Two requests with the same origin and destination, and same departure time. + * - First, A is submitted, then B. + * - Vehicle picks up and A and B, then drops off A and B. + * - In total two stops (P,D) + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("requestA", 1, 1, 5, 5, 2000.0, 1000.0) // + .addRequest("requestB", 1, 1, 5, 5, 2000.0, 1100.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestA"); + assertEquals(1000.0, requestInfo.submissionTime, 1e-3); + assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2230.0, requestInfo.dropoffTime, 1e-3); + } + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestB"); + assertEquals(1100.0, requestInfo.submissionTime, 1e-3); + assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2230.0, requestInfo.dropoffTime, 1e-3); + } + + // Two stops, 1x pickup, 1x dropoff + assertEquals(2, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count()); + } + + @Test + public void sameTrip_extendPickupDuration() { + /*- + * Two requests with the same origin and destination, different departure times. + * - First, A is submitted with departure time 2000 + * - Then, B is submitted with departure time 2020 + * - Scheduling A then B would give a total duration of 60 + 60 = 120s stop duration + * - Scheduling A then merging in B gives a total duration of 60 + 20 = 80s stop duration + * - Expected behavior is to merge the requests + * - In total two stops (P,D) + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("requestA", 1, 1, 5, 5, 2000.0, 1000.0) // + .addRequest("requestB", 1, 1, 5, 5, 2020.0, 1100.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestA"); + assertEquals(1000.0, requestInfo.submissionTime, 1e-3); + // We always add 1s for first pickup + assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + // We have additional 19s because stop task is extended! + assertEquals(2230.0 + 19.0, requestInfo.dropoffTime, 1e-3); + } + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestB"); + assertEquals(1100.0, requestInfo.submissionTime, 1e-3); + // Second pickup does not have one second offset + assertEquals(2020.0 + 60.0 + 0.0, requestInfo.pickupTime, 1e-3); + assertEquals(2230.0 + 19.0, requestInfo.dropoffTime, 1e-3); + } + + // Two stops, 1x pickup, 1x dropoff + assertEquals(2, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count()); + } + + @Test + public void sameTrip_splitPickup() { + /*- + * Two requests with the same origin and destination, different departure times. + * - First, A is submitted with departure time 2000 + * - Then, B is submitted with departure time 2080 + * - Stop for A goes from 2000 to 2060 + * - Stop for B goes from 2080 to 20140 + * - The requests don't overlap, so we schedule individual stops (in a more extreme use case we could schedule things in between then) + * - In total three stops (P, P, D) + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("requestA", 1, 1, 5, 5, 2000.0, 1000.0) // + .addRequest("requestB", 1, 1, 5, 5, 2080.0, 1100.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestA"); + assertEquals(1000.0, requestInfo.submissionTime, 1e-3); + // Pickup as planned + assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + + // Dropoff a bit later than before because we pickup another one on the way + assertEquals(2310.0, requestInfo.dropoffTime, 1e-3); + } + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestB"); + assertEquals(1100.0, requestInfo.submissionTime, 1e-3); + // We insert a later pickup + assertEquals(2080.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + + // Dropoff as planned + assertEquals(2310.0, requestInfo.dropoffTime, 1e-3); + } + + // Three stops, 2x pickup, 1x dropoff + assertEquals(3, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count()); + } + + @Test + public void sameTrip_inverseSubmission_noPrepending() { + /*- + * Two requests with the same origin and destination, different departure times. + * - First, A is submitted with departure time 2020 + * - Then, B is submitted with departure time 2000 + * + * - This is inverse of sameTrip_extendPickupDuration above, the request with the earlier departure + * time is submitted second. + * - We would expect the requests to be merged, but this is not so trivial with the current code base: + * It would be necessary to shift the stop task to the past and extend it. Theoretically, this is + * possible but it would be nice to embed this in a more robust way in the code base. + * - Plus, it may change the current DRT behavior because we have these situations also in non-prebooked + * simulations: We may submit an immediate request and find an insertion for a stop task that starts + * in 5 seconds. This case is treated in standard DRT as any other request (extending the task but + * keeping the start time fixed). + * - We follow this default behaviour here: Instead of *prepending* the task with the new request, we + * *append* it as usual. If there is at least one pickup in the stop task with the standard stop + * durations, there is no additional cost of adding the request, but the person experiences a wait + * delay that could be minimized if we were able to prepend the task. + * - (Then again, do we actually want to do this, it would reserve the vehicle a few seconds more than + * necessary for a stop that is potentially prebooked in a long time). + * + * - Expected behavior in current implementation: Merge new request with the later departing existing + * one, which generates wait time for the customer. + * + * - In total two stops (P, D) + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("requestA", 1, 1, 5, 5, 2020.0, 1000.0) // + .addRequest("requestB", 1, 1, 5, 5, 2000.0, 1100.0) // + .configure(600.0, 1.3, 600.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestA"); + assertEquals(1000.0, requestInfo.submissionTime, 1e-3); + assertEquals(2020.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2250.0, requestInfo.dropoffTime, 1e-3); + } + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestB"); + assertEquals(1100.0, requestInfo.submissionTime, 1e-3); + assertEquals(2000.0 + 20.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2250.0, requestInfo.dropoffTime, 1e-3); + } + + // Three stops, 1x pickup, 1x dropoff + assertEquals(2, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count()); + } + + @Test + public void sameTrip_inverseSubmission_splitPickup() { + /*- + * Two requests with the same origin and destination, different departure times. + * - First, A is submitted with departure time 2020 + * - Then, B is submitted with departure time 1770 + * - B's departure is 250s before A, so appending it to A would lead to more than 300s + * of wait time, which is not a valid insertion + * - Hence, the insertion *after* the pickup of A is not valid + * - But the insertion *before* A is possible (it is usually evaluated but dominated by the insertion after) + * - TODO: May think this through and eliminate these insertion upfront + * + * - Expectation: Standard scheduling a prebooked request before another one (as if they had not the + * same origin link). + * - In total three stops (P,P,D) + */ + + PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) // + .addVehicle("vehicleA", 1, 1) // + .addRequest("requestA", 1, 1, 5, 5, 2020.0, 1000.0) // + .addRequest("requestB", 1, 1, 5, 5, 1470.0, 1100.0) // + .configure(300.0, 2.0, 1800.0, 60.0) // + .endTime(10.0 * 3600.0); + + Controler controller = environment.build(); + installPrebooking(controller); + controller.run(); + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestA"); + assertEquals(1000.0, requestInfo.submissionTime, 1e-3); + assertEquals(2020.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2249.0 + 1.0, requestInfo.dropoffTime, 1e-3); + } + + { + RequestInfo requestInfo = environment.getRequestInfo().get("requestB"); + assertEquals(1100.0, requestInfo.submissionTime, 1e-3); + assertEquals(1470.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3); + assertEquals(2249.0 + 1.0, requestInfo.dropoffTime, 1e-3); + } + + // Three stops, 2x pickup, 1x dropoff + assertEquals(3, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count()); + } +} 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 new file mode 100644 index 00000000000..52c959cd384 --- /dev/null +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java @@ -0,0 +1,438 @@ +package org.matsim.contrib.drt.prebooking; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.tuple.Pair; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.network.NetworkFactory; +import org.matsim.api.core.v01.network.Node; +import org.matsim.api.core.v01.population.Activity; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.api.core.v01.population.Population; +import org.matsim.api.core.v01.population.PopulationFactory; +import org.matsim.contrib.drt.optimizer.insertion.DrtInsertionSearchParams; +import org.matsim.contrib.drt.optimizer.insertion.selective.SelectiveInsertionSearchParams; +import org.matsim.contrib.drt.passenger.events.DrtRequestSubmittedEvent; +import org.matsim.contrib.drt.passenger.events.DrtRequestSubmittedEventHandler; +import org.matsim.contrib.drt.prebooking.logic.AttributeBasedPrebookingLogic; +import org.matsim.contrib.drt.routing.DrtRoute; +import org.matsim.contrib.drt.routing.DrtRouteFactory; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.drt.run.DrtConfigs; +import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; +import org.matsim.contrib.drt.run.MultiModeDrtModule; +import org.matsim.contrib.dvrp.fleet.DvrpVehicle; +import org.matsim.contrib.dvrp.fleet.FleetSpecification; +import org.matsim.contrib.dvrp.fleet.FleetSpecificationImpl; +import org.matsim.contrib.dvrp.fleet.ImmutableDvrpVehicleSpecification; +import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEvent; +import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEventHandler; +import org.matsim.contrib.dvrp.passenger.PassengerPickedUpEvent; +import org.matsim.contrib.dvrp.passenger.PassengerPickedUpEventHandler; +import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent; +import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEventHandler; +import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule; +import org.matsim.contrib.dvrp.run.DvrpConfigGroup; +import org.matsim.contrib.dvrp.run.DvrpModule; +import org.matsim.contrib.dvrp.run.DvrpQSimComponents; +import org.matsim.contrib.dvrp.vrpagent.TaskEndedEvent; +import org.matsim.contrib.dvrp.vrpagent.TaskEndedEventHandler; +import org.matsim.contrib.dvrp.vrpagent.TaskStartedEvent; +import org.matsim.contrib.dvrp.vrpagent.TaskStartedEventHandler; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.QSimConfigGroup.EndtimeInterpretation; +import org.matsim.core.config.groups.QSimConfigGroup.StarttimeInterpretation; +import org.matsim.core.config.groups.ScoringConfigGroup.ActivityParams; +import org.matsim.core.config.groups.ScoringConfigGroup.ModeParams; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.testcases.MatsimTestUtils; + +public class PrebookingTestEnvironment { + private final MatsimTestUtils utils; + + private final int width = 10; + private final int height = 10; + + private final double edgeLength = 200.0; + private final double speed = 10.0; + + private int vehicleCapacity = 4; + + private double maximumWaitTime = 3600.0; + private double detourRelative = 1.3; + private double detourAbsolute = 300.0; + private double stopDuration = 60.0; + private double endTime = 30.0 * 3600.0; + + public PrebookingTestEnvironment(MatsimTestUtils utils) { + this.utils = utils; + } + + public PrebookingTestEnvironment configure(double maximumWaitTime, double detourRelative, double detourAbsolute, + double stopDuration) { + this.maximumWaitTime = maximumWaitTime; + this.detourRelative = detourRelative; + this.detourAbsolute = detourAbsolute; + this.stopDuration = stopDuration; + return this; + } + + public PrebookingTestEnvironment endTime(double endTime) { + this.endTime = endTime; + return this; + } + + private class Request { + String personId; + + Pair origin = null; + Pair destination = null; + + double departureTime; + double submissionTime; + double plannedDepartureTime; + } + + List requests = new LinkedList<>(); + + public PrebookingTestEnvironment addRequest(String personId, int originX, int originY, int destinationX, + int destinationY, double departureTime, double submissionTime, double plannedDepartureTime) { + Request request = new Request(); + request.personId = personId; + request.origin = Pair.of(originX, originY); + request.destination = Pair.of(destinationX, destinationY); + request.departureTime = departureTime; + request.submissionTime = submissionTime; + request.plannedDepartureTime = plannedDepartureTime; + requests.add(request); + return this; + } + + public PrebookingTestEnvironment addRequest(String personId, int originX, int originY, int destinationX, + int destinationY, double departureTime, double submissionTime) { + return addRequest(personId, originX, originY, destinationX, destinationY, departureTime, submissionTime, + Double.NaN); + } + + public PrebookingTestEnvironment addRequest(String personId, int originX, int originY, int destinationX, + int destinationY, double departureTime) { + return addRequest(personId, originX, originY, destinationX, destinationY, departureTime, Double.NaN, + Double.NaN); + } + + private class Vehicle { + String vehicleId; + Pair depot; + } + + private List vehicles = new LinkedList<>(); + + public PrebookingTestEnvironment addVehicle(String vehicleId, int depotX, int depotY) { + Vehicle vehicle = new Vehicle(); + vehicle.vehicleId = vehicleId; + vehicle.depot = Pair.of(depotX, depotY); + vehicles.add(vehicle); + return this; + } + + public PrebookingTestEnvironment setVehicleCapacity(int vehicleCapacity) { + this.vehicleCapacity = vehicleCapacity; + return this; + } + + public Controler build() { + Config config = ConfigUtils.createConfig(); + buildConfig(config); + + Scenario scenario = ScenarioUtils.createScenario(config); + + scenario.getPopulation().getFactory().getRouteFactories().setRouteFactory(DrtRoute.class, + new DrtRouteFactory()); + + buildNetwork(scenario); + buildPopulation(scenario); + + Controler controller = new Controler(scenario); + buildController(controller); + buildFleet(controller); + + configureRequestListener(controller); + configureVehicleListener(controller); + + return controller; + } + + private void buildFleet(Controler controller) { + FleetSpecification fleetSpecification = new FleetSpecificationImpl(); + + for (Vehicle vehicle : vehicles) { + fleetSpecification.addVehicleSpecification(ImmutableDvrpVehicleSpecification.newBuilder() // + .id(Id.create(vehicle.vehicleId, DvrpVehicle.class)) // + .capacity(vehicleCapacity) // + .serviceBeginTime(0.0) // + .serviceEndTime(endTime) // + .startLinkId(createLinkId(createNodeId(vehicle.depot.getLeft(), vehicle.depot.getRight()), + createNodeId(vehicle.depot.getLeft() + 1, vehicle.depot.getRight()))) // + .build()); + } + + controller.addOverridingModule(new AbstractDvrpModeModule("drt") { + @Override + public void install() { + bindModal(FleetSpecification.class).toInstance(fleetSpecification); + } + }); + } + + private void buildController(Controler controller) { + controller.addOverridingModule(new DvrpModule()); + controller.addOverridingModule(new MultiModeDrtModule()); + + MultiModeDrtConfigGroup multiModeDrtConfigGroup = MultiModeDrtConfigGroup.get(controller.getConfig()); + controller.configureQSimComponents(DvrpQSimComponents.activateAllModes(multiModeDrtConfigGroup)); + } + + private void buildConfig(Config config) { + config.controller().setOutputDirectory(utils.getOutputDirectory()); + config.controller().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + config.controller().setLastIteration(0); + + config.qsim().setStartTime(0.0); + config.qsim().setSimStarttimeInterpretation(StarttimeInterpretation.onlyUseStarttime); + + config.qsim().setEndTime(endTime); + config.qsim().setSimEndtimeInterpretation(EndtimeInterpretation.onlyUseEndtime); + + ModeParams drtParams = new ModeParams("drt"); + config.scoring().addModeParams(drtParams); + + ActivityParams genericParams = new ActivityParams("generic"); + genericParams.setScoringThisActivityAtAll(false); + config.scoring().addActivityParams(genericParams); + + DvrpConfigGroup dvrpConfig = new DvrpConfigGroup(); + config.addModule(dvrpConfig); + + MultiModeDrtConfigGroup drtConfig = new MultiModeDrtConfigGroup(); + config.addModule(drtConfig); + + DrtConfigGroup modeConfig = new DrtConfigGroup(); + drtConfig.addParameterSet(modeConfig); + modeConfig.mode = "drt"; + modeConfig.maxWaitTime = maximumWaitTime; + modeConfig.maxTravelTimeAlpha = detourRelative; + modeConfig.maxTravelTimeBeta = detourAbsolute; + modeConfig.stopDuration = stopDuration; + modeConfig.idleVehiclesReturnToDepots = false; + modeConfig.vehiclesFile = null; + + DrtInsertionSearchParams searchParams = new SelectiveInsertionSearchParams(); + modeConfig.addDrtInsertionSearchParams(searchParams); + + DrtConfigs.adjustMultiModeDrtConfig(drtConfig, config.scoring(), config.routing()); + } + + private void buildPopulation(Scenario scenario) { + Population population = scenario.getPopulation(); + PopulationFactory factory = population.getFactory(); + + for (Request request : requests) { + Id personId = Id.createPersonId(request.personId); + + Id originLinkId = createLinkId(createNodeId(request.origin.getLeft(), request.origin.getRight()), + createNodeId(request.origin.getLeft() + 1, request.origin.getRight())); + Id destinationLinkId = createLinkId( + createNodeId(request.destination.getLeft(), request.destination.getRight()), + createNodeId(request.destination.getLeft() + 1, request.destination.getRight())); + + Person person = factory.createPerson(personId); + population.addPerson(person); + + Plan plan = factory.createPlan(); + person.addPlan(plan); + + Activity firstActivity = factory.createActivityFromLinkId("generic", originLinkId); + plan.addActivity(firstActivity); + firstActivity.setEndTime(request.departureTime); + + Leg firstLeg = factory.createLeg("drt"); + plan.addLeg(firstLeg); + + if (!Double.isNaN(request.submissionTime)) { + firstActivity.getAttributes().putAttribute(AttributeBasedPrebookingLogic.getSubmissionAttribute("drt"), + request.submissionTime); + } + + if (!Double.isNaN(request.plannedDepartureTime)) { + firstActivity.getAttributes().putAttribute( + AttributeBasedPrebookingLogic.getPlannedDepartureAttribute("drt"), + request.plannedDepartureTime); + } + + Activity secondActivity = factory.createActivityFromLinkId("generic", destinationLinkId); + plan.addActivity(secondActivity); + } + } + + private Id createNodeId(int i, int j) { + return Id.createNodeId(i + ":" + j); + } + + private Id createLinkId(Id startId, Id endId) { + return Id.createLinkId(startId.toString() + "-" + endId.toString()); + } + + private void buildNetwork(Scenario scenario) { + Network network = scenario.getNetwork(); + NetworkFactory factory = network.getFactory(); + + Node[][] nodes = new Node[width][height]; + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + nodes[i][j] = factory.createNode(createNodeId(i, j), new Coord(i * edgeLength, j * edgeLength)); + network.addNode(nodes[i][j]); + } + } + + // Horizontal + for (int j = 0; j < height; j++) { + for (int i = 0; i < width - 1; i++) { + Node firstNode = nodes[i][j]; + Node secondNode = nodes[i + 1][j]; + + Id forwardId = createLinkId(firstNode.getId(), secondNode.getId()); + Id backwardId = createLinkId(secondNode.getId(), firstNode.getId()); + + Link forwardLink = factory.createLink(forwardId, firstNode, secondNode); + Link backwardLink = factory.createLink(backwardId, secondNode, firstNode); + + network.addLink(forwardLink); + network.addLink(backwardLink); + } + } + + // Vertical + for (int i = 0; i < width; i++) { + for (int j = 0; j < height - 1; j++) { + Node firstNode = nodes[i][j]; + Node secondNode = nodes[i][j + 1]; + + Id forwardId = createLinkId(firstNode.getId(), secondNode.getId()); + Id backwardId = createLinkId(secondNode.getId(), firstNode.getId()); + + Link forwardLink = factory.createLink(forwardId, firstNode, secondNode); + Link backwardLink = factory.createLink(backwardId, secondNode, firstNode); + + network.addLink(forwardLink); + network.addLink(backwardLink); + } + } + + // Defaults + for (Link link : network.getLinks().values()) { + link.setFreespeed(speed); + link.setCapacity(1e9); + link.setLength(edgeLength); + link.setNumberOfLanes(1.0); + } + } + + // ANALYSIS PART + + public class RequestInfo { + public boolean rejected = false; + + public double submissionTime = Double.NaN; + public double pickupTime = Double.NaN; + public double dropoffTime = Double.NaN; + } + + private Map requestInfo = new HashMap<>(); + + public Map getRequestInfo() { + return requestInfo; + } + + private void configureRequestListener(Controler controller) { + controller.addOverridingModule(new AbstractModule() { + @Override + public void install() { + addEventHandlerBinding().toInstance(new RequestListener()); + } + }); + } + + private class RequestListener implements DrtRequestSubmittedEventHandler, PassengerPickedUpEventHandler, + PassengerDroppedOffEventHandler, PassengerRequestRejectedEventHandler { + @Override + public void handleEvent(DrtRequestSubmittedEvent event) { + requestInfo.computeIfAbsent(event.getPersonId().toString(), id -> new RequestInfo()).submissionTime = event + .getTime(); + } + + @Override + public void handleEvent(PassengerRequestRejectedEvent event) { + requestInfo.computeIfAbsent(event.getPersonId().toString(), id -> new RequestInfo()).rejected = true; + } + + @Override + public void handleEvent(PassengerPickedUpEvent event) { + requestInfo.computeIfAbsent(event.getPersonId().toString(), id -> new RequestInfo()).pickupTime = event + .getTime(); + } + + @Override + public void handleEvent(PassengerDroppedOffEvent event) { + requestInfo.computeIfAbsent(event.getPersonId().toString(), id -> new RequestInfo()).dropoffTime = event + .getTime(); + } + } + + public class TaskInfo { + public double startTime = Double.NaN; + public double endTime = Double.NaN; + public String type; + } + + private Map> taskInfo = new HashMap<>(); + + public Map> getTaskInfo() { + return taskInfo; + } + + private void configureVehicleListener(Controler controller) { + controller.addOverridingModule(new AbstractModule() { + @Override + public void install() { + addEventHandlerBinding().toInstance(new VehicleListener()); + } + }); + } + + private class VehicleListener implements TaskStartedEventHandler, TaskEndedEventHandler { + @Override + public void handleEvent(TaskStartedEvent event) { + taskInfo.computeIfAbsent(event.getDvrpVehicleId().toString(), id -> new LinkedList<>()).add(new TaskInfo()); + taskInfo.get(event.getDvrpVehicleId().toString()).getLast().startTime = event.getTime(); + taskInfo.get(event.getDvrpVehicleId().toString()).getLast().type = event.getTaskType().name(); + } + + @Override + public void handleEvent(TaskEndedEvent event) { + taskInfo.get(event.getDvrpVehicleId().toString()).getLast().endTime = event.getTime(); + } + } +} diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java index fe7d10d9b7b..b95f6d8de61 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java @@ -20,6 +20,7 @@ package org.matsim.contrib.drt.run.examples; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; import java.io.IOException; import java.net.URL; @@ -36,18 +37,26 @@ import org.matsim.contrib.drt.optimizer.DrtRequestInsertionRetryParams; import org.matsim.contrib.drt.optimizer.insertion.repeatedselective.RepeatedSelectiveInsertionSearchParams; import org.matsim.contrib.drt.optimizer.insertion.selective.SelectiveInsertionSearchParams; +import org.matsim.contrib.drt.prebooking.PrebookingParams; +import org.matsim.contrib.drt.prebooking.logic.ProbabilityBasedPrebookingLogic; +import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.run.DrtControlerCreator; import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; import org.matsim.contrib.drt.stops.CorrectedStopTimeCalculator; import org.matsim.contrib.drt.stops.CumulativeStopTimeCalculator; import org.matsim.contrib.drt.stops.MinimumStopDurationAdapter; -import org.matsim.contrib.drt.stops.StaticPassengerStopDurationProvider; import org.matsim.contrib.drt.stops.PassengerStopDurationProvider; +import org.matsim.contrib.drt.stops.StaticPassengerStopDurationProvider; import org.matsim.contrib.drt.stops.StopTimeCalculator; +import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent; +import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEventHandler; +import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEvent; +import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEventHandler; import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule; import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.AbstractModule; import org.matsim.core.controler.Controler; import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; import org.matsim.core.utils.io.IOUtils; @@ -296,12 +305,55 @@ public void install() { controller.run(); + // sh, 11/08/2023: updated after introducing prebookg, basically we generate a + // new feasible insertion (see InsertionGenerator) that previously did not + // exist, but has the same cost (pickup loss + drop-off loss) as the original + // one var expectedStats = Stats.newBuilder() .rejectionRate(0.04) .rejections(16) - .waitAverage(278.92) - .inVehicleTravelTimeMean(384.6) - .totalTravelTimeMean(663.52) + .waitAverage(278.76) + .inVehicleTravelTimeMean(384.93) + .totalTravelTimeMean(663.68) + .build(); + + verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats); + } + + @Test + public void testRunDrtWithPrebooking() { + Id.resetCaches(); + URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), + "mielec_drt_config.xml"); + + Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(), + new OTFVisConfigGroup()); + + config.controller().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + config.controller().setOutputDirectory(utils.getOutputDirectory()); + + DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(config); + drtConfig.addParameterSet(new PrebookingParams()); + + Controler controller = DrtControlerCreator.createControler(config, false); + ProbabilityBasedPrebookingLogic.install(controller, drtConfig, 0.5, 4.0 * 3600.0); + + PrebookingTracker tracker = new PrebookingTracker(); + tracker.install(controller); + + controller.run(); + + assertEquals(157, tracker.immediateScheduled); + assertEquals(205, tracker.prebookedScheduled); + assertEquals(26, tracker.immediateRejected); + assertEquals(0, tracker.prebookedRejected); + + var expectedStats = Stats.newBuilder() + .rejectionRate(0.07) + .rejections(26) + .waitAverage(232.76) + .inVehicleTravelTimeMean(389.09) + .totalTravelTimeMean(621.85) .build(); verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats); @@ -415,4 +467,40 @@ public Stats build() { } } } + + static private class PrebookingTracker implements PassengerRequestRejectedEventHandler, PassengerRequestScheduledEventHandler { + int immediateScheduled = 0; + int prebookedScheduled = 0; + int immediateRejected = 0; + int prebookedRejected = 0; + + @Override + public void handleEvent(PassengerRequestScheduledEvent event) { + if (event.getRequestId().toString().contains("prebooked")) { + prebookedScheduled++; + } else { + immediateScheduled++; + } + } + + @Override + public void handleEvent(PassengerRequestRejectedEvent event) { + if (event.getRequestId().toString().contains("prebooked")) { + prebookedRejected++; + } else { + immediateRejected++; + } + } + + void install(Controler controller) { + PrebookingTracker thisTracker = this; + + controller.addOverridingModule(new AbstractModule() { + @Override + public void install() { + addEventHandlerBinding().toInstance(thisTracker); + } + }); + } + } } diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/speedup/DrtSpeedUpTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/speedup/DrtSpeedUpTest.java index 37ec10d3eda..0cc447f1237 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/speedup/DrtSpeedUpTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/speedup/DrtSpeedUpTest.java @@ -271,7 +271,7 @@ private void updateRequestAnalyser(EventSequence... eventSequences) { private EventSequence eventSequence(String id, double submittedTime, double waitTime, double inVehicleSpeed) { var requestId = Id.create(id, Request.class); var submittedEvent = new DrtRequestSubmittedEvent(submittedTime, MODE, requestId, null, linkAB.getId(), - linkBC.getId(), Double.NaN, Double.NaN, Double.NaN, Double.NaN); + linkBC.getId(), Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN); var pickupEvent = new PassengerPickedUpEvent(submittedTime + waitTime, MODE, requestId, null, null); double rideTime = DistanceUtils.calculateDistance(linkBC, linkAB) / inVehicleSpeed; var dropoffEvent = new PassengerDroppedOffEvent(submittedTime + waitTime + rideTime, MODE, requestId, null, diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/util/DrtEventsReadersTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/util/DrtEventsReadersTest.java index 43510e98db7..afc39c2e6de 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/util/DrtEventsReadersTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/util/DrtEventsReadersTest.java @@ -64,7 +64,7 @@ public class DrtEventsReadersTest { //standard dvrp events are tested in DvrpEventsReadersTest private final List drtEvents = List.of( - new DrtRequestSubmittedEvent(0, mode, request, person, link1, link2, 111, 222, 412.0, 512.0),// + new DrtRequestSubmittedEvent(0, mode, request, person, link1, link2, 111, 222, 0.0, 412.0, 512.0),// taskStarted(10, DrtDriveTask.TYPE, 0, link1),// taskEnded(30, DefaultDrtStopTask.TYPE, 1, link2), // taskStarted(50, DrtStayTask.TYPE, 2, link1),// diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/analysis/VehicleOccupancyProfileCalculator.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/analysis/VehicleOccupancyProfileCalculator.java index 8104898b89e..16a8136492c 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/analysis/VehicleOccupancyProfileCalculator.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/analysis/VehicleOccupancyProfileCalculator.java @@ -164,10 +164,9 @@ public TimeDiscretizer getTimeDiscretizer() { private void increment(VehicleState state, double endTime) { Verify.verify(state.taskType != null); Verify.verify(state.occupancy >= 0); + Verify.verify(state.occupancy <= maxCapacity); - boolean servingPassengers = passengerServingTaskTypes.contains(state.taskType); - Verify.verify(servingPassengers || state.occupancy == 0, - "Vehicles not serving passengers must not be occupied"); + boolean servingPassengers = passengerServingTaskTypes.contains(state.taskType) || state.occupancy > 0; double[] profile = servingPassengers ? vehicleOccupancyProfiles.get(state.occupancy) : diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiModule.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiModule.java index 1bbe061f8d7..ab5916c967e 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiModule.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiModule.java @@ -25,6 +25,7 @@ import org.matsim.api.core.v01.TransportMode; import org.matsim.contrib.dvrp.fleet.FleetModule; import org.matsim.contrib.dvrp.optimizer.VrpOptimizer; +import org.matsim.contrib.dvrp.passenger.AdvanceRequestProvider; import org.matsim.contrib.dvrp.passenger.DefaultPassengerRequestValidator; import org.matsim.contrib.dvrp.passenger.PassengerEngineQSimModule; import org.matsim.contrib.dvrp.passenger.PassengerEngineQSimModule.PassengerEngineType; @@ -87,6 +88,9 @@ protected void configureQSim() { // converts scheduled tasks into simulated actions (legs and activities) bindModal(VrpAgentLogic.DynActionCreator.class).to(OneTaxiActionCreator.class).in(Singleton.class); + + // no advance request + bindModal(AdvanceRequestProvider.class).toInstance(AdvanceRequestProvider.NONE); } }); } diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AbstractPassengerRequestEvent.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AbstractPassengerRequestEvent.java index e7d8df4a56c..ca269d5975a 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AbstractPassengerRequestEvent.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AbstractPassengerRequestEvent.java @@ -43,7 +43,7 @@ public abstract class AbstractPassengerRequestEvent extends Event implements Has private final Id requestId; private final Id personId; - AbstractPassengerRequestEvent(double time, String mode, Id requestId, Id personId) { + protected AbstractPassengerRequestEvent(double time, String mode, Id requestId, Id personId) { super(time); this.mode = mode; this.requestId = requestId; diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AdvanceRequestProvider.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AdvanceRequestProvider.java new file mode 100644 index 00000000000..56426adb184 --- /dev/null +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AdvanceRequestProvider.java @@ -0,0 +1,13 @@ +package org.matsim.contrib.dvrp.passenger; + +import javax.annotation.Nullable; + +import org.matsim.api.core.v01.population.Leg; +import org.matsim.core.mobsim.framework.MobsimAgent; + +public interface AdvanceRequestProvider { + static public AdvanceRequestProvider NONE = (MobsimAgent agent, Leg leg) -> null; + + @Nullable + PassengerRequest retrieveRequest(MobsimAgent agent, Leg leg); +} diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java index 12600006799..428d2cdd91f 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java @@ -20,13 +20,11 @@ package org.matsim.contrib.dvrp.passenger; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import jakarta.inject.Inject; -import jakarta.inject.Provider; - import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; @@ -45,7 +43,9 @@ import org.matsim.core.modal.ModalProviders; import com.google.common.base.Preconditions; -import com.google.common.base.Verify; + +import jakarta.inject.Inject; +import jakarta.inject.Provider; /** * @author Michal Maciejewski (michalm) @@ -54,6 +54,7 @@ public final class DefaultPassengerEngine implements PassengerEngine, PassengerR private final String mode; private final MobsimTimer mobsimTimer; + private final EventsManager eventsManager; private final PassengerRequestCreator requestCreator; private final VrpOptimizer optimizer; @@ -61,24 +62,30 @@ public final class DefaultPassengerEngine implements PassengerEngine, PassengerR private final PassengerRequestValidator requestValidator; private final InternalPassengerHandling internalPassengerHandling; + private final AdvanceRequestProvider advanceRequestProvider; private InternalInterface internalInterface; //accessed in doSimStep() and handleDeparture() (no need to sync) private final Map, MobsimPassengerAgent> activePassengers = new HashMap<>(); + + // holds vehicle stop activities for requests that have not arrived at departure point yet + private final Map, PassengerPickupActivity> waitingForPassenger = new HashMap<>(); //accessed in doSimStep() and handleEvent() (potential data races) private final Queue rejectedRequestsEvents = new ConcurrentLinkedQueue<>(); DefaultPassengerEngine(String mode, EventsManager eventsManager, MobsimTimer mobsimTimer, PassengerRequestCreator requestCreator, VrpOptimizer optimizer, Network network, - PassengerRequestValidator requestValidator) { + PassengerRequestValidator requestValidator, AdvanceRequestProvider advanceRequestProvider) { this.mode = mode; this.mobsimTimer = mobsimTimer; this.requestCreator = requestCreator; this.optimizer = optimizer; this.network = network; this.requestValidator = requestValidator; + this.eventsManager = eventsManager; + this.advanceRequestProvider = advanceRequestProvider; internalPassengerHandling = new InternalPassengerHandling(mode, eventsManager); } @@ -95,12 +102,24 @@ public void onPrepareSim() { @Override public void doSimStep(double time) { - while (!rejectedRequestsEvents.isEmpty()) { - MobsimPassengerAgent passenger = activePassengers.remove(rejectedRequestsEvents.poll().getRequestId()); - //not much else can be done for immediate requests - //set the passenger agent to abort - the event will be thrown by the QSim - passenger.setStateToAbort(mobsimTimer.getTimeOfDay()); - internalInterface.arrangeNextAgentState(passenger); + // If prebooked requests are rejected (by the optimizer, through an + // event) after submission, but before departure, the PassengerEngine does not + // know this agent yet. Hence, we wait with setting the state to abort until the + // agent has arrived here (if ever). + + Iterator iterator = rejectedRequestsEvents.iterator(); + + while (iterator.hasNext()) { + PassengerRequestRejectedEvent event = iterator.next(); + MobsimPassengerAgent passenger = activePassengers.remove(event.getRequestId()); + + if (passenger != null) { + // not much else can be done for immediate requests + // set the passenger agent to abort - the event will be thrown by the QSim + passenger.setStateToAbort(mobsimTimer.getTimeOfDay()); + internalInterface.arrangeNextAgentState(passenger); + iterator.remove(); + } } } @@ -118,16 +137,36 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id fromLinkI internalInterface.registerAdditionalAgentOnLink(passenger); Id toLinkId = passenger.getDestinationLinkId(); - - Route route = ((Leg)((PlanAgent)passenger).getCurrentPlanElement()).getRoute(); - PassengerRequest request = requestCreator.createRequest(internalPassengerHandling.createRequestId(), - passenger.getId(), route, getLink(fromLinkId), getLink(toLinkId), now, now); - validateAndSubmitRequest(passenger, request, now); + + // try to find a prebooked requests that is associated to this leg + Leg leg = (Leg) ((PlanAgent) passenger).getCurrentPlanElement(); + PassengerRequest request = advanceRequestProvider.retrieveRequest(agent, leg); + + if (request == null) { // immediate request + Route route = ((Leg)((PlanAgent)passenger).getCurrentPlanElement()).getRoute(); + request = requestCreator.createRequest(internalPassengerHandling.createRequestId(), passenger.getId(), + route, getLink(fromLinkId), getLink(toLinkId), now, now); + + // must come before validateAndSubmitRequest (to come before rejection event) + eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerId())); + activePassengers.put(request.getId(), passenger); + + validateAndSubmitRequest(passenger, request, now); + } else { // advance request + eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerId())); + activePassengers.put(request.getId(), passenger); + + PassengerPickupActivity pickupActivity = waitingForPassenger.remove(request.getId()); + if (pickupActivity != null) { + // the vehicle is already waiting for the request, notify it + pickupActivity.notifyPassengerIsReadyForDeparture(passenger, now); + } + } + return true; } private void validateAndSubmitRequest(MobsimPassengerAgent passenger, PassengerRequest request, double now) { - activePassengers.put(request.getId(), passenger); if (internalPassengerHandling.validateRequest(request, requestValidator, now)) { //need to synchronise to address cases where requestSubmitted() may: // - be called from outside DepartureHandlers @@ -147,13 +186,39 @@ private Link getLink(Id linkId) { linkId, mode); } + /** + * There are two ways of interacting with the PassengerEngine: + * + * - (1) The stop activity tries to pick up a passenger and receives whether the + * pickup succeeded or not (see tryPickUpPassenger). In the classic + * implementation, the vehicle only calls tryPickUpPassenger at the time when it + * actually wants to pick up the person (at the end of the activity). It may + * happen that the person is not present yet. In that case, the pickup request + * is saved and notifyPassengerReady is called on the stop activity upen + * departure of the agent. + * + * - (2) If pickup and dropoff times are handled more flexibly by the stop + * activity, it might want to detect whether an agent is ready to be picked up, + * then start an "interaction time" and only after perform the actual pickup. + * For that purpose, we have queryPickUpPassenger, which indicates whether the + * agent is already there, and, if not, makes sure that the stop activity is + * notified once the agent arrives for departure. + */ + @Override + public boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId) { + if (!activePassengers.containsKey(requestId)) { + waitingForPassenger.put(requestId, pickupActivity); + return false; + } + + return true; + } + @Override public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId, double now) { - boolean pickedUp = internalPassengerHandling.tryPickUpPassenger(driver, activePassengers.get(requestId), + return internalPassengerHandling.tryPickUpPassenger(driver, activePassengers.get(requestId), requestId, now); - Verify.verify(pickedUp, "Not possible without prebooking"); - return pickedUp; } @Override @@ -180,7 +245,8 @@ public static Provider createProvider(String mode) { public DefaultPassengerEngine get() { return new DefaultPassengerEngine(getMode(), eventsManager, mobsimTimer, getModalInstance(PassengerRequestCreator.class), getModalInstance(VrpOptimizer.class), - getModalInstance(Network.class), getModalInstance(PassengerRequestValidator.class)); + getModalInstance(Network.class), getModalInstance(PassengerRequestValidator.class), + getModalInstance(AdvanceRequestProvider.class)); } }; } diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineWithPrebooking.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineWithPrebooking.java index 3e6fb064d00..354bb788a6f 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineWithPrebooking.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineWithPrebooking.java @@ -62,6 +62,7 @@ public final class PassengerEngineWithPrebooking private final String mode; private final MobsimTimer mobsimTimer; + private final EventsManager eventsManager; private final PreplanningEngine preplanningEngine; private final PassengerRequestCreator requestCreator; @@ -87,6 +88,7 @@ public final class PassengerEngineWithPrebooking this.optimizer = optimizer; this.network = network; this.requestValidator = requestValidator; + this.eventsManager = eventsManager; internalPassengerHandling = new InternalPassengerHandling(mode, eventsManager); } @@ -164,10 +166,14 @@ private Link getLink(Id linkId) { //TODO what if it was already rejected while prebooking?? PassengerRequest prebookedRequest = prebookedRequests.get(0); + + eventsManager.processEvent(new PassengerWaitingEvent(now, mode, prebookedRequest.getId(), prebookedRequest.getPassengerId())); + PassengerPickupActivity awaitingPickup = awaitingPickups.remove(prebookedRequest.getId()); if (awaitingPickup != null) { awaitingPickup.notifyPassengerIsReadyForDeparture(passenger, now); } + return true; } @@ -294,4 +300,25 @@ public static Provider createProvider(String mode) { } }; } + + /* + * This method has been retrofitted with the new prebooking implementation in + * Nov 2023. Not sure if this is the right way to do it, this class doesn't seem + * to be tested anywhere. /sebhoerl + */ + @Override + public boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId) { + Id linkId = driver.getCurrentLinkId(); + RequestEntry requestEntry = activeRequests.get(requestId); + MobsimPassengerAgent passenger = requestEntry.passenger; + + if (passenger.getCurrentLinkId() != linkId + || passenger.getState() != MobsimAgent.State.LEG + || !passenger.getMode().equals(mode)) { + awaitingPickups.put(requestId, pickupActivity); + return false;// wait for the passenger + } + + return true; // passenger present? + } } diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerHandler.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerHandler.java index 1a9e313731c..408f187db77 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerHandler.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerHandler.java @@ -30,6 +30,8 @@ * This looks quite general. But as of now is a dvrp thing. kai, apr'23 */ public interface PassengerHandler { + boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId); + boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId, double now); diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEvent.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEvent.java new file mode 100644 index 00000000000..6c37f84a9be --- /dev/null +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEvent.java @@ -0,0 +1,53 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2017 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.contrib.dvrp.passenger; + +import java.util.Map; +import java.util.Objects; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.population.Person; +import org.matsim.contrib.dvrp.optimizer.Request; + +/** + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class PassengerWaitingEvent extends AbstractPassengerRequestEvent { + public static final String EVENT_TYPE = "passenger waiting"; + + public PassengerWaitingEvent(double time, String mode, Id requestId, Id personId) { + super(time, mode, requestId, personId); + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + public static PassengerWaitingEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + String mode = Objects.requireNonNull(attributes.get(ATTRIBUTE_MODE)); + Id requestId = Id.create(attributes.get(ATTRIBUTE_REQUEST), Request.class); + Id personId = Id.createPersonId(attributes.get(ATTRIBUTE_PERSON)); + return new PassengerWaitingEvent(time, mode, requestId, personId); + } +} diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEventHandler.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEventHandler.java new file mode 100644 index 00000000000..1ca52e48d2f --- /dev/null +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEventHandler.java @@ -0,0 +1,29 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2017 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.contrib.dvrp.passenger; + +import org.matsim.core.events.handler.EventHandler; + +/** + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public interface PassengerWaitingEventHandler extends EventHandler { + void handleEvent(final PassengerWaitingEvent event); +} diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/RequestQueue.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/RequestQueue.java deleted file mode 100644 index 14859041fe1..00000000000 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/RequestQueue.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * *********************************************************************** * - * project: org.matsim.* - * *********************************************************************** * - * * - * copyright : (C) 2020 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** * - */ - -package org.matsim.contrib.dvrp.passenger; - -import java.util.Collection; -import java.util.Comparator; -import java.util.PriorityQueue; -import java.util.Queue; -import java.util.TreeSet; - -import org.matsim.contrib.dvrp.optimizer.Request; - -/** - * @author Michal Maciejewski (michalm) - */ -public final class RequestQueue { - public static RequestQueue withLimitedAdvanceRequestPlanningHorizon( - double planningHorizon) { - //all immediate and selected advance (i.e. starting within the planning horizon) requests are scheduled - return new RequestQueue<>(planningHorizon); - } - - public static RequestQueue withInfiniteAdvanceRequestPlanningHorizon() { - return new RequestQueue<>(Double.POSITIVE_INFINITY);//all immediate and advance requests are scheduled - } - - public static RequestQueue withNoAdvanceRequestPlanningHorizon() { - return new RequestQueue<>(0);//immediate requests only - } - - private static final Comparator ABSOLUTE_COMPARATOR = Comparator.comparing( - PassengerRequest::getEarliestStartTime) - .thenComparing(PassengerRequest::getLatestStartTime) - .thenComparing(Request::getSubmissionTime) - .thenComparing(Request::getId); - - //all requests in the planning horizon (also includes old requests: never scheduled or unscheduled) - private final Collection schedulableRequests = new TreeSet<>(ABSOLUTE_COMPARATOR); - - //advance requests that are not in the planning horizon - private final Queue postponedRequests = new PriorityQueue<>(ABSOLUTE_COMPARATOR); - - private final double planningHorizon; - - private double lastTimeStep = -Double.MAX_VALUE; - - private RequestQueue(double planningHorizon) { - this.planningHorizon = planningHorizon; - } - - public void updateQueuesOnNextTimeSteps(double currentTime) { - lastTimeStep = currentTime; - while (!postponedRequests.isEmpty() && isSchedulable(postponedRequests.peek())) { - schedulableRequests.add(postponedRequests.poll()); - } - } - - public void addRequest(R request) { - (isSchedulable(request) ? schedulableRequests : postponedRequests).add(request); - } - - private boolean isSchedulable(R request) { - return request.getEarliestStartTime() <= lastTimeStep + planningHorizon; - } - - /** - * Assumes external code can modify schedulableRequests (e.g. remove scheduled requests and add unscheduled ones) - * - * @return requests to be inserted into vehicle schedules - */ - public Collection getSchedulableRequests() { - return schedulableRequests; - } -} diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngine.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngine.java index 0906bed9e71..14374da7168 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngine.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngine.java @@ -144,6 +144,8 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id fromLinkI Route route = ((Leg)((PlanAgent)passenger).getCurrentPlanElement()).getRoute(); PassengerRequest request = requestCreator.createRequest(internalPassengerHandling.createRequestId(), passenger.getId(), route, getLink(fromLinkId), getLink(toLinkId), now, now); + + eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerId())); if (internalPassengerHandling.validateRequest(request, requestValidator, now)) { Route teleportedRoute = adaptLegRouteForTeleportation(passenger, request, now); @@ -156,7 +158,7 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id fromLinkI passenger.setStateToAbort(mobsimTimer.getTimeOfDay()); internalInterface.arrangeNextAgentState(passenger); } - + return true; } @@ -183,6 +185,12 @@ private Link getLink(Id linkId) { linkId, mode); } + @Override + public boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, + Id requestId) { + throw new UnsupportedOperationException("No notifying when teleporting"); + } + @Override public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId, double now) { diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/util/DvrpEventsReaders.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/util/DvrpEventsReaders.java index 2df41bed134..093071c7f08 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/util/DvrpEventsReaders.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/util/DvrpEventsReaders.java @@ -28,6 +28,7 @@ import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent; import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEvent; import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEvent; +import org.matsim.contrib.dvrp.passenger.PassengerWaitingEvent; import org.matsim.contrib.dvrp.schedule.Task; import org.matsim.contrib.dvrp.vrpagent.TaskEndedEvent; import org.matsim.contrib.dvrp.vrpagent.TaskStartedEvent; @@ -41,6 +42,7 @@ public static Map createCustomEven return Map.of(PassengerRequestSubmittedEvent.EVENT_TYPE, PassengerRequestSubmittedEvent::convert,// PassengerRequestScheduledEvent.EVENT_TYPE, PassengerRequestScheduledEvent::convert,// PassengerRequestRejectedEvent.EVENT_TYPE, PassengerRequestRejectedEvent::convert,// + PassengerWaitingEvent.EVENT_TYPE, PassengerWaitingEvent::convert,// PassengerPickedUpEvent.EVENT_TYPE, PassengerPickedUpEvent::convert, // PassengerDroppedOffEvent.EVENT_TYPE, PassengerDroppedOffEvent::convert,// TaskStartedEvent.EVENT_TYPE, e -> TaskStartedEvent.convert(e, stringToTaskTypeConverter), diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java index 6032c73484a..6636119ccd3 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java @@ -100,6 +100,7 @@ public void test_valid_served() { fixture.assertPassengerEvents( new ActivityEndEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), + new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), new PassengerRequestScheduledEvent(departureTime, MODE, requestId, fixture.PERSON_ID, VEHICLE_ID, 0, scheduledDropoffTime), new PersonEntersVehicleEvent(pickupStartTime, fixture.PERSON_ID, Id.createVehicleId(VEHICLE_ID)), @@ -121,6 +122,7 @@ public void test_invalid_rejected() { var requestId = Id.create("taxi_0", Request.class); fixture.assertPassengerEvents(new ActivityEndEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), + new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), new PassengerRequestRejectedEvent(0, MODE, requestId, fixture.PERSON_ID, "invalid"), new PersonStuckEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); } @@ -136,6 +138,7 @@ public void test_valid_rejected() { var requestId = Id.create("taxi_0", Request.class); fixture.assertPassengerEvents(new ActivityEndEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), + new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), new PassengerRequestRejectedEvent(0, MODE, requestId, fixture.PERSON_ID, "rejecting_all_requests"), new PersonStuckEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); } @@ -175,6 +178,7 @@ protected void configureQSim() { bindModal(PassengerRequestCreator.class).to(OneTaxiRequest.OneTaxiRequestCreator.class) .asEagerSingleton(); bindModal(PassengerRequestValidator.class).toInstance(requestValidator); + bindModal(AdvanceRequestProvider.class).toInstance(AdvanceRequestProvider.NONE); //supply addQSimComponentBinding(DynActivityEngine.COMPONENT_NAME).to(DynActivityEngine.class); diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java index d88211dc547..b8153c22ccd 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java @@ -72,6 +72,7 @@ public void test_valid_teleported() { fixture.assertPassengerEvents( new ActivityEndEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), + new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), new PassengerRequestScheduledEvent(departureTime, MODE, requestId, fixture.PERSON_ID, null, departureTime, arrivalTime), new PassengerPickedUpEvent(departureTime, MODE, requestId, fixture.PERSON_ID, null), new PassengerDroppedOffEvent(arrivalTime, MODE, requestId, fixture.PERSON_ID, null), @@ -93,6 +94,7 @@ public void test_invalid_rejected() { fixture.assertPassengerEvents( new ActivityEndEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), + new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), new PassengerRequestRejectedEvent(departureTime, MODE, requestId, fixture.PERSON_ID, "invalid"), new PersonStuckEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); } diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/DefaultTaxiOptimizer.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/DefaultTaxiOptimizer.java index 119d83ed325..0a725e9ba2b 100644 --- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/DefaultTaxiOptimizer.java +++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/DefaultTaxiOptimizer.java @@ -21,13 +21,16 @@ import static org.matsim.contrib.taxi.schedule.TaxiTaskBaseType.OCCUPIED_DRIVE; +import java.util.Collection; +import java.util.Comparator; import java.util.List; +import java.util.TreeSet; import org.matsim.contrib.drt.passenger.DrtRequest; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.fleet.Fleet; import org.matsim.contrib.dvrp.optimizer.Request; -import org.matsim.contrib.dvrp.passenger.RequestQueue; +import org.matsim.contrib.dvrp.passenger.PassengerRequest; import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater; import org.matsim.contrib.dvrp.schedule.Task; import org.matsim.contrib.taxi.run.TaxiConfigGroup; @@ -42,7 +45,11 @@ public class DefaultTaxiOptimizer implements TaxiOptimizer { private final Fleet fleet; private final TaxiScheduler scheduler; - private final RequestQueue unplannedRequests = RequestQueue.withNoAdvanceRequestPlanningHorizon(); + private final Collection unplannedRequests = new TreeSet<>( + Comparator.comparing(PassengerRequest::getEarliestStartTime) // + .thenComparing(PassengerRequest::getLatestStartTime) // + .thenComparing(Request::getSubmissionTime) // + .thenComparing(Request::getId)); private final UnplannedRequestInserter requestInserter; @@ -65,8 +72,7 @@ public DefaultTaxiOptimizer(EventsManager eventsManager, TaxiConfigGroup taxiCfg @Override public void notifyMobsimBeforeSimStep(@SuppressWarnings("rawtypes") MobsimBeforeSimStepEvent e) { - unplannedRequests.updateQueuesOnNextTimeSteps(e.getSimulationTime()); - requiresReoptimization |= !unplannedRequests.getSchedulableRequests().isEmpty(); + requiresReoptimization |= !unplannedRequests.isEmpty(); if (requiresReoptimization && isNewDecisionEpoch(e, params.getReoptimizationTimeStep())) { if (params.doUnscheduleAwaitingRequests) { @@ -81,7 +87,7 @@ public void notifyMobsimBeforeSimStep(@SuppressWarnings("rawtypes") MobsimBefore } } - requestInserter.scheduleUnplannedRequests(unplannedRequests.getSchedulableRequests()); + requestInserter.scheduleUnplannedRequests(unplannedRequests); if (params.doUnscheduleAwaitingRequests && taxiCfg.vehicleDiversion) { scheduler.stopAllAimlessDriveTasks(); @@ -98,12 +104,12 @@ public static boolean isNewDecisionEpoch(@SuppressWarnings("rawtypes") MobsimBef protected void unscheduleAwaitingRequests() { List removedRequests = scheduler.removeAwaitingRequestsFromAllSchedules(); - removedRequests.forEach(unplannedRequests::addRequest); + removedRequests.forEach(unplannedRequests::add); } @Override public void requestSubmitted(Request request) { - unplannedRequests.addRequest((DrtRequest)request); + unplannedRequests.add((DrtRequest)request); } @Override diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/run/MultiModeTaxiModule.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/run/MultiModeTaxiModule.java index f371de6ed8e..31bf3440150 100644 --- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/run/MultiModeTaxiModule.java +++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/run/MultiModeTaxiModule.java @@ -81,7 +81,6 @@ public static DrtConfigGroup convertTaxiToDrtCfg(TaxiConfigGroup taxiCfg) { drtCfg.drtServiceAreaShapeFile = null; drtCfg.plotDetailedCustomerStats = taxiCfg.detailedStats || taxiCfg.timeProfiles; drtCfg.numberOfThreads = taxiCfg.numberOfThreads; - drtCfg.advanceRequestPlanningHorizon = 0; drtCfg.storeUnsharedPath = false; taxiCfg.getTaxiFareParams().ifPresent(taxiFareParams -> { diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz index 9a9086bfb49..88cd4466237 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_events.xml.gz index 47b8b0ef972..16c754539b8 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz index 9a9086bfb49..88cd4466237 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_arrivalTime/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_arrivalTime/output_events.xml.gz index b11fde85025..1607c967ce8 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_arrivalTime/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_arrivalTime/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_dse/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_dse/output_events.xml.gz index 1c43c9c4998..3305da22ddb 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_dse/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_dse/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_pickupTime/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_pickupTime/output_events.xml.gz index a81976085eb..c32da9baf0b 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_pickupTime/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_pickupTime/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_totalWaitTime/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_totalWaitTime/output_events.xml.gz index 967c4ec17f7..f8068a4f3fd 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_totalWaitTime/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_totalWaitTime/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/fifo/FifoTaxiOptimizerIT/testFifo/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/fifo/FifoTaxiOptimizerIT/testFifo/output_events.xml.gz index c3e9bebccaf..26bb2f449dc 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/fifo/FifoTaxiOptimizerIT/testFifo/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/fifo/FifoTaxiOptimizerIT/testFifo/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_dse/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_dse/output_events.xml.gz index 1667897a021..4a779638fde 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_dse/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_dse/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_events.xml.gz index 55264934454..a0312998c04 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minWaitTime/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minWaitTime/output_events.xml.gz index 8d059ca81d3..cea982bfcea 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minWaitTime/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minWaitTime/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_dse/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_dse/output_events.xml.gz index cb64c1a7416..ea41623c65f 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_dse/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_dse/output_events.xml.gz differ diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_minWaitTime/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_minWaitTime/output_events.xml.gz index 886bddefbb5..04e6928409b 100644 Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_minWaitTime/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_minWaitTime/output_events.xml.gz differ diff --git a/contribs/vsp/src/test/java/org/matsim/integration/drtAndPt/PtAlongALine2Test.java b/contribs/vsp/src/test/java/org/matsim/integration/drtAndPt/PtAlongALine2Test.java index ef0cc856096..f9daad794d9 100644 --- a/contribs/vsp/src/test/java/org/matsim/integration/drtAndPt/PtAlongALine2Test.java +++ b/contribs/vsp/src/test/java/org/matsim/integration/drtAndPt/PtAlongALine2Test.java @@ -235,7 +235,6 @@ public void testPtAlongALineWithRaptorAndDrtServiceArea() { drtConfigGroup.maxWaitTime = Double.MAX_VALUE; drtConfigGroup.rejectRequestIfMaxWaitOrTravelTimeViolated = false; drtConfigGroup.useModeFilteredSubnetwork = true; - drtConfigGroup.advanceRequestPlanningHorizon = 99999; drtConfigGroup.addParameterSet(new ExtensiveInsertionSearchParams()); mm.addParameterSet(drtConfigGroup);