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 1f3b8624806..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 @@ -170,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), drtCfg.scheduleWaitBeforeDrive))) + getter.getModal(StopTimeCalculator.class), scheduleWaitBeforeDrive))) .asEagerSingleton(); bindModal(DrtOfferAcceptor.class).toInstance(DrtOfferAcceptor.DEFAULT_ACCEPTOR); @@ -192,7 +193,8 @@ public EmptyVehicleRelocator get() { })).in(Singleton.class); bindModal(EDrtActionCreator.class).toProvider(modalProvider(getter -> { - VrpAgentLogic.DynActionCreator delegate = drtCfg.prebooking ? getter.getModal(PrebookingActionCreator.class) + VrpAgentLogic.DynActionCreator delegate = drtCfg.getPrebookingParams().isPresent() + ? getter.getModal(PrebookingActionCreator.class) : getter.getModal(DrtActionCreator.class); // EDrtActionCreator wraps around delegate and initializes consumption trackers 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 036579406eb..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 @@ -75,7 +75,7 @@ protected void configureQSim() { timer); })).in(Singleton.class); - Preconditions.checkState(!drtCfg.prebooking, "cannot use preplanned schedules with prebooking"); + 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 b964fab1fb5..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,7 +21,8 @@ import java.net.URL; import org.junit.Test; -import org.matsim.contrib.drt.prebooking.logic.FixedSharePrebookingLogic; +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; @@ -51,10 +52,10 @@ public void testWithPrebooking() { new OTFVisConfigGroup(), new EvConfigGroup()); DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(config); - drtConfig.prebooking = true; + drtConfig.addParameterSet(new PrebookingParams()); Controler controller = RunEDrtScenario.createControler(config, false); - FixedSharePrebookingLogic.install(drtConfig.mode, 0.5, 4.0 * 3600.0, controller); + ProbabilityBasedPrebookingLogic.install(controller, drtConfig, 0.5, 4.0 * 3600.0); controller.run(); } 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 cff85138aab..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 @@ -138,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), drtCfg.scheduleWaitBeforeDrive))) + getter.getModal(StopTimeCalculator.class), scheduleWaitBeforeDrive))) .asEagerSingleton(); bindModal(DrtOfferAcceptor.class).toInstance(DrtOfferAcceptor.DEFAULT_ACCEPTOR); @@ -160,7 +161,7 @@ public EmptyVehicleRelocator get() { timer); })).in(Singleton.class); - if (!drtCfg.prebooking) { + if (drtCfg.getPrebookingParams().isEmpty()) { bindModal(VrpAgentLogic.DynActionCreator.class).to(modalKey(DrtActionCreator.class)); } else { bindModal(VrpAgentLogic.DynActionCreator.class).to(modalKey(PrebookingActionCreator.class)); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java index 10bbb5fc950..3c25ff15430 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java @@ -125,7 +125,7 @@ private void processQueue(double now) { if (prebookingLegIndex <= currentLegIndex) { violations = new HashSet<>(violations); - violations.add("past leg"); + violations.add("past leg"); // the leg for which the booking was made has already happened } if (!violations.isEmpty()) { diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java index a291b472cae..4c423d64ce4 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java @@ -1,6 +1,9 @@ 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; @@ -10,6 +13,7 @@ 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; @@ -39,5 +43,14 @@ protected void configureQSim() { 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/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/AttributePrebookingLogic.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/AttributePrebookingLogic.java deleted file mode 100644 index 1d589842732..00000000000 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/AttributePrebookingLogic.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.matsim.contrib.drt.prebooking.logic; - -import org.matsim.api.core.v01.population.Activity; -import org.matsim.api.core.v01.population.Leg; -import org.matsim.api.core.v01.population.PlanElement; -import org.matsim.api.core.v01.population.Population; -import org.matsim.contrib.drt.prebooking.PrebookingManager; -import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; -import org.matsim.core.controler.Controler; -import org.matsim.core.mobsim.qsim.QSim; -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 class represents a prebooking logic that will search for the - * "prebooking:submissionTime" 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" 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. - * - * Note that this logic is aimed towards scenarios where each agent has one trip - * (trip-based simulation). There is no logic that handles the special case - * where an agent may submit a request in the morning and another one for the - * afternoon, but the first one is rejected. - * - * @author Sebastian Hörl (sebhoerl), IRT SystemX - */ -public class AttributePrebookingLogic extends TimedPrebookingLogic { - static public final String SUBMISSION_TIME_ATTRIBUTE = "prebooking:submissionTime"; - static public final String PLANNED_DEPARTURE_ATTRIBUTE = "prebooking:plannedDepartureTime"; - - private final String mode; - - private final Population population; - private final TimeInterpretation timeInterpretation; - private final QSim qsim; - - public AttributePrebookingLogic(String mode, PrebookingManager prebookingManager, Population population, - TimeInterpretation timeInterpretation, QSim qsim) { - super(prebookingManager); - - this.mode = mode; - this.population = population; - this.timeInterpretation = timeInterpretation; - this.qsim = qsim; - } - - @Override - protected void scheduleRequests() { - PopulationIterator iterator = PopulationIterator.create(population, qsim); - - while (iterator.hasNext()) { - var item = iterator.next(); - - TimeTracker timeTracker = new TimeTracker(timeInterpretation); - - Double submissionTime = null; - Double plannedDepartureTime = null; - boolean foundLeg = false; - - for (PlanElement element : item.plan().getPlanElements()) { - if (element instanceof Activity) { - Activity activity = (Activity) element; - - if (!TripStructureUtils.isStageActivityType(activity.getType())) { - foundLeg = false; - submissionTime = (Double) activity.getAttributes().getAttribute(SUBMISSION_TIME_ATTRIBUTE); - plannedDepartureTime = (Double) activity.getAttributes() - .getAttribute(PLANNED_DEPARTURE_ATTRIBUTE); - } - } - - if (element instanceof Leg) { - Leg leg = (Leg) element; - - if (leg.getMode().equals(mode)) { - Preconditions.checkState(!foundLeg, "Person " + item.agent().getId().toString() - + " has at least two drt legs in one trip."); - foundLeg = true; - - if (plannedDepartureTime == null) { - plannedDepartureTime = timeTracker.getTime().seconds(); - } - - if (submissionTime != null) { - queue.schedule(submissionTime, item.agent(), leg, plannedDepartureTime); - } - } - } - - timeTracker.addElement(element); - } - } - } - - static public void install(String mode, Controler controller) { - controller.addOverridingQSimModule(new AbstractDvrpModeQSimModule(mode) { - @Override - protected void configureQSim() { - addModalQSimComponentBinding().toProvider(modalProvider(getter -> { - PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class); - Population population = getter.get(Population.class); - TimeInterpretation timeInterpretation = TimeInterpretation.create(getConfig()); - QSim qsim = getter.get(QSim.class); - - return new AttributePrebookingLogic(mode, prebookingManager, population, timeInterpretation, qsim); - })); - } - }); - } - -} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/FixedSharePrebookingLogic.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/FixedSharePrebookingLogic.java deleted file mode 100644 index 6b7eeb61a5d..00000000000 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/FixedSharePrebookingLogic.java +++ /dev/null @@ -1,102 +0,0 @@ -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.api.core.v01.population.Population; -import org.matsim.contrib.drt.prebooking.PrebookingManager; -import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; -import org.matsim.core.controler.Controler; -import org.matsim.core.mobsim.qsim.QSim; -import org.matsim.core.utils.timing.TimeInterpretation; -import org.matsim.core.utils.timing.TimeTracker; - -/** - * 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. - * - * Note that this logic is aimed towards scenarios where each agent has one trip - * (trip-based simulation). There is no logic that handles the special case - * where an agent may submit a request in the morning and another one for the - * afternoon, but the first one is rejected. - * - * @author Sebastian Hörl (sebhoerl), IRT SystemX - */ -public class FixedSharePrebookingLogic extends TimedPrebookingLogic { - private final String mode; - - private final QSim qsim; - private final TimeInterpretation timeInterpretation; - private final Population population; - - private final double prebookingProbability; - private final double submissionSlack; - - private final long randomSeed; - - public FixedSharePrebookingLogic(String mode, double prebookingProbability, double submissionSlack, - PrebookingManager prebookingManager, Population population, TimeInterpretation timeInterpretation, - long randomSeed, QSim qsim) { - super(prebookingManager); - - this.mode = mode; - this.prebookingProbability = prebookingProbability; - this.submissionSlack = submissionSlack; - this.timeInterpretation = timeInterpretation; - this.randomSeed = randomSeed; - this.qsim = qsim; - this.population = population; - - } - - @Override - protected void scheduleRequests() { - Random random = new Random(randomSeed); - - PopulationIterator iterator = PopulationIterator.create(population, qsim); - - while (iterator.hasNext()) { - var item = iterator.next(); - - TimeTracker timeTracker = new TimeTracker(timeInterpretation); - - for (PlanElement element : item.plan().getPlanElements()) { - if (element instanceof Leg) { - Leg leg = (Leg) element; - - if (leg.getMode().equals(mode) && random.nextDouble() < prebookingProbability) { - double earliestDepartureTime = leg.getDepartureTime().seconds(); - double submissionTime = Double.isFinite(submissionSlack) - ? leg.getDepartureTime().seconds() - submissionSlack - : timeInterpretation.getSimulationStartTime(); - queue.schedule(submissionTime, item.agent(), leg, earliestDepartureTime); - } - } - - timeTracker.addElement(element); - } - } - } - - static public void install(String mode, double prebookingProbability, double submissionSlack, - Controler controller) { - controller.addOverridingQSimModule(new AbstractDvrpModeQSimModule(mode) { - @Override - protected void configureQSim() { - addModalQSimComponentBinding().toProvider(modalProvider(getter -> { - PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class); - Population population = getter.get(Population.class); - TimeInterpretation timeInterpretation = TimeInterpretation.create(getConfig()); - QSim qsim = getter.get(QSim.class); - - return new FixedSharePrebookingLogic(mode, prebookingProbability, submissionSlack, - prebookingManager, population, timeInterpretation, getConfig().global().getRandomSeed(), - qsim); - })); - } - }); - } -} 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/TimedPrebookingLogic.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/TimedPrebookingLogic.java deleted file mode 100644 index 6bf0b9c9d7e..00000000000 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/TimedPrebookingLogic.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.matsim.contrib.drt.prebooking.logic; - -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -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.events.MobsimInitializedEvent; -import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener; -import org.matsim.core.mobsim.framework.listeners.MobsimInitializedListener; - -/** - * This class helps to define a PrebookingLogic where it is known in advance - * when requests will be submitted. In essence, we list all prebooked requests - * at the beginning of the simulation and define a specific time at which they - * should be submitted. See AttributePrebookingLogic or - * FixedSharePrebookingLogic as examples. - * - * @author Sebastian Hörl (sebhoerl), IRT SystemX - */ -public abstract class TimedPrebookingLogic implements MobsimInitializedListener, MobsimBeforeSimStepListener { - protected final PrebookingManager prebookingManager; - protected final PrebookingQueue queue = new PrebookingQueue(); - - protected TimedPrebookingLogic(PrebookingManager prebookingManager) { - this.prebookingManager = prebookingManager; - } - - @Override - public void notifyMobsimInitialized(@SuppressWarnings("rawtypes") MobsimInitializedEvent e) { - queue.clear(); - scheduleRequests(); - } - - protected abstract void scheduleRequests(); - - @Override - public void notifyMobsimBeforeSimStep(@SuppressWarnings("rawtypes") MobsimBeforeSimStepEvent event) { - queue.getScheduledItems(event.getSimulationTime()).forEach(item -> { - prebookingManager.prebook(item.agent(), item.leg(), item.departuretime()); - }); - } - - protected class PrebookingQueue { - private PriorityQueue queue = new PriorityQueue<>(); - private int sequence = 0; - - public void schedule(double submissionTime, MobsimAgent agent, Leg leg, double departureTime) { - queue.add(new ScheduledItem(submissionTime, agent, leg, departureTime, sequence++)); - } - - public Collection getScheduledItems(double time) { - List batch = new LinkedList<>(); - - while (!queue.isEmpty() && queue.peek().submissionTime <= time) { - batch.add(queue.poll()); - } - - return batch; - } - - public void clear() { - queue.clear(); - sequence = 0; - } - - } - - private record ScheduledItem(double submissionTime, MobsimAgent agent, Leg leg, double departuretime, int sequence) - implements Comparable { - @Override - public int compareTo(ScheduledItem o) { - // sort by submissionTime, but otherwise keep the order of submission - int comparison = Double.compare(submissionTime, o.submissionTime); - - if (comparison != 0) { - return comparison; - } - - return Integer.compare(sequence, o.sequence); - } - } -} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PopulationIterator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PopulationIterator.java similarity index 72% rename from contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PopulationIterator.java rename to contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PopulationIterator.java index 1ea1c0474b5..faf2c1ce87c 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/PopulationIterator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/logic/helpers/PopulationIterator.java @@ -1,11 +1,11 @@ -package org.matsim.contrib.drt.prebooking.logic; +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.PopulationIterator.PersonItem; +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; @@ -41,7 +41,17 @@ public PersonItem next() { public record PersonItem(MobsimAgent agent, Plan plan) { } - static public PopulationIterator create(Population population, QSim qsim) { - return new PopulationIterator(population, qsim); + 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 d5e16c00957..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,19 +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 - @Parameter - @Comment("When working with prebooked requests, 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 - - @Parameter - @Comment("Enables prebooking functionality") - public boolean prebooking = false; - @NotNull private DrtInsertionSearchParams drtInsertionSearchParams; @@ -219,6 +207,9 @@ public enum OperationalScheme { @Nullable private DrtSpeedUpParams drtSpeedUpParams; + @Nullable + private PrebookingParams prebookingParams; + @Nullable private DrtRequestInsertionRetryParams drtRequestInsertionRetryParams; @@ -259,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 @@ -334,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 e265d0e7452..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 @@ -96,7 +96,7 @@ public void install() { return new DefaultStopTimeCalculator(drtCfg.stopDuration); })).in(Singleton.class); - if (!drtCfg.prebooking) { + if (drtCfg.getPrebookingParams().isEmpty()) { bindModal(StopTimeCalculator.class).toProvider(modalProvider(getter -> { return new DefaultStopTimeCalculator(drtCfg.stopDuration); })).in(Singleton.class); 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 3205dd91ede..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 @@ -76,7 +76,7 @@ protected void configureQSim() { install(optimizerQSimModule); } - if (drtCfg.prebooking) { + if (drtCfg.getPrebookingParams().isPresent()) { install(new PrebookingModeQSimModule(getMode())); bindModal(AdvanceRequestProvider.class).to(modalKey(PrebookingManager.class)); } else { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java index 267f08fff77..63e4e1bae45 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java @@ -5,7 +5,7 @@ import org.junit.Rule; import org.junit.Test; import org.matsim.contrib.drt.prebooking.PrebookingTestEnvironment.RequestInfo; -import org.matsim.contrib.drt.prebooking.logic.AttributePrebookingLogic; +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; @@ -52,8 +52,8 @@ public void withoutPrebookedRequests() { private void installPrebooking(Controler controller) { DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(controller.getConfig()); - drtConfig.prebooking = true; - AttributePrebookingLogic.install("drt", controller); + drtConfig.addParameterSet(new PrebookingParams()); + AttributeBasedPrebookingLogic.install(controller, drtConfig); } @Test diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java index a0849ffde31..52c959cd384 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java @@ -23,7 +23,7 @@ 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.AttributePrebookingLogic; +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; @@ -62,7 +62,7 @@ public class PrebookingTestEnvironment { private final MatsimTestUtils utils; - + private final int width = 10; private final int height = 10; @@ -76,7 +76,7 @@ public class PrebookingTestEnvironment { private double detourAbsolute = 300.0; private double stopDuration = 60.0; private double endTime = 30.0 * 3600.0; - + public PrebookingTestEnvironment(MatsimTestUtils utils) { this.utils = utils; } @@ -272,12 +272,13 @@ private void buildPopulation(Scenario scenario) { plan.addLeg(firstLeg); if (!Double.isNaN(request.submissionTime)) { - firstActivity.getAttributes().putAttribute(AttributePrebookingLogic.SUBMISSION_TIME_ATTRIBUTE, + firstActivity.getAttributes().putAttribute(AttributeBasedPrebookingLogic.getSubmissionAttribute("drt"), request.submissionTime); } if (!Double.isNaN(request.plannedDepartureTime)) { - firstActivity.getAttributes().putAttribute(AttributePrebookingLogic.PLANNED_DEPARTURE_ATTRIBUTE, + firstActivity.getAttributes().putAttribute( + AttributeBasedPrebookingLogic.getPlannedDepartureAttribute("drt"), request.plannedDepartureTime); } 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 c8147b65b05..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 @@ -37,15 +37,16 @@ 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.logic.FixedSharePrebookingLogic; +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; @@ -332,10 +333,10 @@ public void testRunDrtWithPrebooking() { config.controller().setOutputDirectory(utils.getOutputDirectory()); DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(config); - drtConfig.prebooking = true; + drtConfig.addParameterSet(new PrebookingParams()); Controler controller = DrtControlerCreator.createControler(config, false); - FixedSharePrebookingLogic.install(drtConfig.mode, 0.5, 4.0 * 3600.0, controller); + ProbabilityBasedPrebookingLogic.install(controller, drtConfig, 0.5, 4.0 * 3600.0); PrebookingTracker tracker = new PrebookingTracker(); tracker.install(controller);