diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/BasicDrtAbortHandler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/BasicDrtAbortHandler.java new file mode 100644 index 00000000000..d2ad77c6bfb --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/BasicDrtAbortHandler.java @@ -0,0 +1,168 @@ +package org.matsim.contrib.drt.optimizer.abort; + +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.population.*; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; +import org.matsim.contrib.dvrp.path.VrpPaths; +import org.matsim.core.config.Config; +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.framework.MobsimTimer; +import org.matsim.core.mobsim.qsim.AbortHandler; +import org.matsim.core.mobsim.qsim.InternalInterface; +import org.matsim.core.mobsim.qsim.agents.WithinDayAgentUtils; +import org.matsim.core.mobsim.qsim.interfaces.MobsimEngine; +import org.matsim.core.population.routes.GenericRouteImpl; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility; +import org.matsim.core.router.speedy.SpeedyALTFactory; +import org.matsim.core.router.util.LeastCostPathCalculator; +import org.matsim.core.router.util.TravelTime; + +import java.util.*; + +/** + * Rejected DRT requests will be teleported to the destination based on max travel time + *

+ * Author: kai + * */ +public class BasicDrtAbortHandler implements AbortHandler, MobsimEngine { + public static final String COMPONENT_NAME = "DrtAbortHandler"; + public static final String walkAfterRejectMode = "walkAfterReject"; + private static final String delimiter = "============"; + @Inject + Network network; + @Inject + Population population; + @Inject + MobsimTimer mobsimTimer; + + TravelTime travelTime; + LeastCostPathCalculator router; + private static final Logger log = LogManager.getLogger(BasicDrtAbortHandler.class ); + private InternalInterface internalInterface; + private final List agents = new ArrayList<>(); + + List drtModes = new ArrayList<>(); + Map alphas = new HashMap<>(); + Map betas = new HashMap<>(); + + @Inject + BasicDrtAbortHandler(Network network, Map travelTimeMap, Config config) { + travelTime = travelTimeMap.get(TransportMode.car); + router = new SpeedyALTFactory().createPathCalculator(network, new OnlyTimeDependentTravelDisutility(travelTime), travelTime); + + for (DrtConfigGroup modalElement : MultiModeDrtConfigGroup.get(config).getModalElements()) { + drtModes.add(modalElement.mode); + alphas.put(modalElement.mode, modalElement.maxTravelTimeAlpha); + betas.put(modalElement.mode, modalElement.maxTravelTimeBeta); + } + } + + @Override + public boolean handleAbort(MobsimAgent agent) { + log.warn("need to handle abort of agent=" + agent); + PopulationFactory pf = population.getFactory(); + + if (drtModes.contains(agent.getMode())) { + Plan plan = WithinDayAgentUtils.getModifiablePlan(agent); + + printPlan("\n current plan=", plan); + + int index = WithinDayAgentUtils.getCurrentPlanElementIndex(agent); + + Id interactionLink = agent.getCurrentLinkId(); + + double now = mobsimTimer.getTimeOfDay(); + + Leg leg = (Leg) plan.getPlanElements().get(index); + + Id originallyPlannedDestinationLink = leg.getRoute().getEndLinkId(); + + // (1) The current leg needs to be modified so that it ends at the current location. And one should somehow tag this + // as a failed drt trip. (There is presumably already a drt rejected event, so this info could also be reconstructed.) + { + leg.setDepartureTime(now); + leg.setTravelTime(0); + leg.setRoute(pf.getRouteFactories().createRoute(GenericRouteImpl.class, interactionLink, interactionLink)); + // (startLinkId and endLinkId are _only_ in the route) + } + + // (2) An interaction activity needs to be inserted. + { + Coord interactionCoord = network.getLinks().get(interactionLink).getCoord(); +// Activity activity = PopulationUtils.createStageActivityFromCoordLinkIdAndModePrefix( interactionCoord, interactionLink, walkAfterRejectMode ); + Activity activity = pf.createActivityFromCoord(TripStructureUtils.createStageActivityType(walkAfterRejectMode), interactionCoord); + activity.setMaximumDuration(1.); + plan.getPlanElements().add(index + 1, activity); + // (inserts at given position; pushes everything else forward) + } + + // (3) There needs to be a new teleportation leg from here to there. + { +// Leg secondLeg = pf.createLeg( walkAfterRejectMode+"_"+agent.getMode() ); + Leg secondLeg = pf.createLeg(walkAfterRejectMode); + secondLeg.setDepartureTime(now); + + double directTravelTime = VrpPaths.calcAndCreatePath + (network.getLinks().get(interactionLink), network.getLinks().get(originallyPlannedDestinationLink), now, router, travelTime).getTravelTime(); + double estimatedTravelTime = alphas.get(agent.getMode()) * directTravelTime + betas.get(agent.getMode()); + secondLeg.setTravelTime(estimatedTravelTime); + secondLeg.setRoute(pf.getRouteFactories().createRoute(GenericRouteImpl.class, interactionLink, originallyPlannedDestinationLink)); + plan.getPlanElements().add(index + 2, secondLeg); + + } + + // (4) reset the agent caches: + WithinDayAgentUtils.resetCaches(agent); + + // (5) add the agent to an internal list, which is processed during doSimStep, which formally ends the current + // (aborted) leg, and moves the agent forward in its state machine. + agents.add(agent); + + printPlan("plan after splicing=", plan); + } + return true; + } + + @Override + public void doSimStep(double time) { + for (MobsimAgent agent : agents) { + agent.endLegAndComputeNextState(time); + // (we haven't actually thrown an abort event, and are planning not to do this here. We probably have thrown a drt + // rejected event. The "endLeg..." method will throw a person arrival event.) + + this.internalInterface.arrangeNextAgentState(agent); + } + agents.clear(); + } + + @Override + public void onPrepareSim() { + } + + @Override + public void afterSim() { + } + + @Override + public void setInternalInterface(InternalInterface internalInterface) { + this.internalInterface = internalInterface; + } + + private static void printPlan(String x, Plan plan) { + log.warn(delimiter); + log.warn(x + plan); + for (PlanElement planElement : plan.getPlanElements()) { + log.warn(planElement); + } + log.warn(delimiter); + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/DrtRejectionEventHandler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/DrtRejectionEventHandler.java new file mode 100644 index 00000000000..31837b623d7 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/DrtRejectionEventHandler.java @@ -0,0 +1,88 @@ +package org.matsim.contrib.drt.optimizer.abort; + +import com.google.inject.Inject; +import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.events.PersonScoreEvent; +import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent; +import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEventHandler; +import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEvent; +import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEventHandler; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.listener.IterationEndsListener; + +import java.util.HashMap; +import java.util.Map; + +public class DrtRejectionEventHandler implements PassengerRequestRejectedEventHandler, + PassengerRequestSubmittedEventHandler, IterationEndsListener { + private static final Logger log = LogManager.getLogger(DrtRejectionEventHandler.class ); + private final Map numberOfRejectionsPerTimeBin = new HashMap<>(); + private final Map numberOfSubmissionsPerTimeBin = new HashMap<>(); + private final Map probabilityOfRejectionPerTimeBin = new HashMap<>(); + + // Key parameters + // TODO: consider make them configurable + private final double timeBinSize = 900; + // Time bin to analyze the probability of being rejected + private final double rejectionCost = 6; + // 6 -> 1 hour of default performing score + private final double learningRate = 0.25; + // (1 - alpha) * old probability + alpha * new probability (0 < alpha <= 1) + + @Inject + private EventsManager events; + + @Override + public void handleEvent(PassengerRequestRejectedEvent event) { + // Currently, we assume there is only 1 DRT operator with standard DRT mode ("drt") + // Because it is a little tricky to get DRT Config Group here (which can only be acquired via DvrpQSimModule), + // we just use the simple way. For multi-operator, a map can be introduced to store the data for different DRT modes + if (event.getMode().equals(TransportMode.drt)) { + int timeBin = getTimeBin(event.getTime()); + numberOfRejectionsPerTimeBin.computeIfAbsent(timeBin, c -> new MutableInt()).increment(); + } + } + + @Override + public void reset(int iteration) { + PassengerRequestRejectedEventHandler.super.reset(iteration); + if (iteration != 0) { + log.debug(probabilityOfRejectionPerTimeBin.values()); + numberOfSubmissionsPerTimeBin.clear(); + numberOfRejectionsPerTimeBin.clear(); + } + } + + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + // Calculate the probability of being rejected at each time bin + for (Integer timeBin : numberOfSubmissionsPerTimeBin.keySet()) { + double probability = numberOfRejectionsPerTimeBin.getOrDefault(timeBin, new MutableInt()).doubleValue() / + numberOfSubmissionsPerTimeBin.get(timeBin).doubleValue(); + // Apply exponential discount + probability = learningRate * probability + (1 - learningRate) * probabilityOfRejectionPerTimeBin.getOrDefault(timeBin, 0.); + probabilityOfRejectionPerTimeBin.put(timeBin, probability); + } + } + + @Override + public void handleEvent(PassengerRequestSubmittedEvent event) { + if (event.getMode().equals(TransportMode.drt)) { + int timeBin = getTimeBin(event.getTime()); + numberOfSubmissionsPerTimeBin.computeIfAbsent(timeBin, c -> new MutableInt()).increment(); + + // Add a cost for potential rejection + double extraScore = (-1) * rejectionCost * probabilityOfRejectionPerTimeBin.getOrDefault(getTimeBin(event.getTime()), 0.); + events.processEvent(new PersonScoreEvent(event.getTime(), event.getPersonId(), extraScore, "Potential_of_being_rejected")); + } + } + + private int getTimeBin(double time) { + return (int) (Math.floor(time / timeBinSize)); + } + +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/DrtRejectionModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/DrtRejectionModule.java new file mode 100644 index 00000000000..25592c4d254 --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/abort/DrtRejectionModule.java @@ -0,0 +1,25 @@ +package org.matsim.contrib.drt.optimizer.abort; + +import com.google.inject.Singleton; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import org.matsim.core.mobsim.qsim.components.QSimComponentsConfigGroup; + +public class DrtRejectionModule extends AbstractModule { + @Override + public void install() { + ConfigUtils.addOrGetModule(this.getConfig(), QSimComponentsConfigGroup.class).addActiveComponent(BasicDrtAbortHandler.COMPONENT_NAME); + + bind(DrtRejectionEventHandler.class).in(Singleton.class); + addEventHandlerBinding().to(DrtRejectionEventHandler.class); + addControlerListenerBinding().to(DrtRejectionEventHandler.class); + + this.installQSimModule(new AbstractQSimModule() { + @Override + protected void configureQSim() { + this.addQSimComponentBinding(BasicDrtAbortHandler.COMPONENT_NAME).to(BasicDrtAbortHandler.class); + } + }); + } +} diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/examples/RunDrtAbortExample.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/examples/RunDrtAbortExample.java new file mode 100644 index 00000000000..3869770498a --- /dev/null +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/examples/RunDrtAbortExample.java @@ -0,0 +1,16 @@ +package org.matsim.contrib.drt.run.examples; + +import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; +import org.matsim.contrib.dvrp.run.DvrpConfigGroup; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.vis.otfvis.OTFVisConfigGroup; + +public class RunDrtAbortExample { + public static void main(String[] args) { + String configUrl = args[0]; + Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(), + new OTFVisConfigGroup() ); + } + +} diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/DrtAbortTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/DrtAbortTest.java new file mode 100644 index 00000000000..c1c1b3aacf4 --- /dev/null +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/DrtAbortTest.java @@ -0,0 +1,180 @@ +package org.matsim.contrib.drt.run.examples; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.contrib.drt.optimizer.abort.DrtRejectionModule; +import org.matsim.contrib.drt.run.*; +import org.matsim.contrib.dvrp.run.DvrpConfigGroup; +import org.matsim.contrib.dvrp.run.DvrpModule; +import org.matsim.contrib.dvrp.run.DvrpQSimComponents; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.ReplanningConfigGroup; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.replanning.strategies.DefaultPlanStrategiesModule; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.utils.io.IOUtils; +import org.matsim.examples.ExamplesUtils; +import org.matsim.testcases.MatsimTestUtils; +import org.matsim.vis.otfvis.OTFVisConfigGroup; + +import java.net.URL; + +import static org.matsim.contrib.drt.optimizer.abort.BasicDrtAbortHandler.walkAfterRejectMode; +import static org.matsim.core.config.groups.ScoringConfigGroup.*; + +public class DrtAbortTest{ + private static final Logger log = LogManager.getLogger(DrtAbortTest.class ); + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + +// static final String walkAfterRejectMode = "walkAfterReject"; + + enum Variant {simpleTest, iterations, benchmark }; + + @Test public void testAbortHandler() { + run( Variant.simpleTest); + } + + @Test public void testIterations() { + run( Variant.iterations ); + } + + @Test public void testBenchmark() { + run( Variant.benchmark ); + } + + private void run( Variant variant) { + Id.resetCaches(); + URL configUrl = IOUtils.extendUrl( ExamplesUtils.getTestScenarioURL("mielec" ), "mielec_drt_config.xml" ); + Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(), + new OTFVisConfigGroup() ); + + boolean rejectionModule = true; + config.controller().setLastIteration(50); + config.plans().setInputFile("plans_only_drt_4.0.xml.gz"); +// config.global().setRandomSeed(9999); + { + ReplanningConfigGroup.StrategySettings settings = new ReplanningConfigGroup.StrategySettings(); + settings.setStrategyName(DefaultPlanStrategiesModule.DefaultStrategy.ChangeSingleTripMode); + settings.setWeight(0.1); + config.replanning().addStrategySettings(settings); + config.replanning().setFractionOfIterationsToDisableInnovation(0.9); + config.replanning().setMaxAgentPlanMemorySize(5); + } + { + config.changeMode().setModes( new String[] { TransportMode.drt, TransportMode.bike }); + } + { + ModeParams params = new ModeParams( TransportMode.bike ); + params.setMarginalUtilityOfTraveling(-6.); + config.scoring().addModeParams( params ); + } + + for ( DrtConfigGroup drtCfg : MultiModeDrtConfigGroup.get( config ).getModalElements()) { +// drtCfg.vehiclesFile = "vehicles-2-cap-4.xml"; + drtCfg.vehiclesFile = "vehicles-10-cap-4.xml"; +// drtCfg.vehiclesFile = "vehicles-20-cap-2.xml"; + + drtCfg.maxTravelTimeAlpha = 1.5; + drtCfg.maxTravelTimeBeta = 600.; + drtCfg.maxWaitTime = 300.; + drtCfg.stopDuration = 10.; + } + + switch ( variant ) { + case simpleTest -> { + config.controller().setLastIteration(1); + config.plans().setInputFile("plans_only_drt_rejection_test.xml"); + // Chengqi: I have created a special plan for the rejection handler test: 3 requests within 1 time bin (6:45 - 7:00) + for ( DrtConfigGroup drtCfg : MultiModeDrtConfigGroup.get( config ).getModalElements()) { + drtCfg.vehiclesFile = "vehicles-rejection-test.xml"; + // Chengqi: I have created a special vehicle file for the rejection handler test: 1 vehicle locates at the departure place of one request + + drtCfg.maxTravelTimeAlpha = 1.2; + drtCfg.maxTravelTimeBeta = 100.; + drtCfg.maxWaitTime = 10.; + drtCfg.stopDuration = 1.; + // (Trying to force abort(s); can't say if this is the correct syntax. kai, apr'23) + // Chengqi: With this parameter, 2 out of the 3 requests during 6:45-7:00 will be rejected + // -> 2/3 probability of being rejected -> 2/3 of penalty to everyone who submit DRT requests + // Based on current setup, at iteration 1, we should see person score event for each person + // with a negative score of -6: 12 (base penalty) * 2/3 (probability) * 0.75 (learning rate, current) + 0 (previous penalty) * 0.25 (learning rate, previous) + // Currently a manual check is performed and passed. Perhaps an integrated test can be implemented here (TODO). + } + } + case benchmark -> rejectionModule = false; + case iterations -> { + } + // What do we want to see? + + // In early iterations, we want many (maybe 20% or 50%) drt_teleported (because of rejections). + + // In late iterations, we want few drt_teleported (because of mode choice). + + // Need to look at the numbers. + + // There should be a certain rejection rate in a given time bin. That should translate into a penalty. The penalty should be reasonable for us. + + // The drt_teleported score should be plausible. + + default -> throw new IllegalStateException("Unexpected value: " + variant); + } + + config.controller().setOverwriteFileSetting( OutputDirectoryHierarchy.OverwriteFileSetting.deleteDirectoryIfExists ); + config.controller().setOutputDirectory(utils.getOutputDirectory()); + + config.scoring().addActivityParams( new ActivityParams( TripStructureUtils.createStageActivityType( walkAfterRejectMode ) ).setScoringThisActivityAtAll( false ) ); + config.scoring().addModeParams( new ModeParams( walkAfterRejectMode ) ); + + config.scoring().setWriteExperiencedPlans( true ); + +// for ( DrtConfigGroup drtCfg : MultiModeDrtConfigGroup.get(config ).getModalElements()) { + //relatively high max age to prevent rejections +// var drtRequestInsertionRetryParams = new DrtRequestInsertionRetryParams(); +// drtRequestInsertionRetryParams.maxRequestAge = 7200; +// drtCfg.addParameterSet(drtRequestInsertionRetryParams); + // I don't know what the above does; might be useful to understand. +// } + + MultiModeDrtConfigGroup multiModeDrtConfig = MultiModeDrtConfigGroup.get( config ); + DrtConfigs.adjustMultiModeDrtConfig(multiModeDrtConfig, config.scoring(), config.routing() ); + + Scenario scenario = DrtControlerCreator.createScenarioWithDrtRouteFactory( config ); + ScenarioUtils.loadScenario(scenario ); + + Controler controler = new Controler(scenario); + controler.addOverridingModule(new DvrpModule() ); + controler.addOverridingModule(new MultiModeDrtModule() ); + controler.configureQSimComponents( DvrpQSimComponents.activateAllModes(multiModeDrtConfig ) ); + + if (rejectionModule){ + controler.addOverridingModule( new DrtRejectionModule() ); + } + + controler.run(); + + // yy I cannot say if the expected status is useful here. kai, apr'23 + + var expectedStats = RunDrtExampleIT.Stats.newBuilder() + .rejectionRate(1.0) + .rejections(1) + .waitAverage(Double.NaN) + .inVehicleTravelTimeMean(Double.NaN) + .totalTravelTimeMean(Double.NaN) + .build(); + + // Chengqi: I commented this line, because NaN cannot be checked (NaN == NaN always false) +// RunDrtExampleIT.verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats); + } + + +} 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..1304ef1f8d3 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 @@ -25,14 +25,22 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.junit.Rule; import org.junit.Test; +import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.population.*; 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; @@ -50,18 +58,36 @@ import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.Controler; import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.core.gbl.Gbl; +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.framework.MobsimPassengerAgent; +import org.matsim.core.mobsim.framework.MobsimTimer; +import org.matsim.core.mobsim.framework.PassengerAgent; +import org.matsim.core.mobsim.qsim.AbortHandler; +import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import org.matsim.core.mobsim.qsim.InternalInterface; +import org.matsim.core.mobsim.qsim.agents.WithinDayAgentUtils; +import org.matsim.core.mobsim.qsim.components.QSimComponentsConfigGroup; +import org.matsim.core.mobsim.qsim.interfaces.MobsimEngine; +import org.matsim.core.modal.AbstractModalQSimModule; +import org.matsim.core.population.PopulationUtils; +import org.matsim.core.population.routes.GenericRouteImpl; +import org.matsim.core.router.TripStructureUtils; import org.matsim.core.utils.io.IOUtils; import org.matsim.examples.ExamplesUtils; import org.matsim.testcases.MatsimTestUtils; import org.matsim.vis.otfvis.OTFVisConfigGroup; import com.google.common.base.MoreObjects; +import org.matsim.withinday.utils.EditPlans; +import org.matsim.withinday.utils.EditTrips; /** * @author jbischoff * @author Sebastian Hörl, IRT SystemX (sebhoerl) */ public class RunDrtExampleIT { + private static final Logger log = LogManager.getLogger(RunDrtExampleIT.class); @Rule public MatsimTestUtils utils = new MatsimTestUtils(); @@ -181,12 +207,12 @@ public void testRunDrtExampleWithRequestRetry() { RunDrtExample.run(config, false); var expectedStats = Stats.newBuilder() - .rejectionRate(0.0) - .rejections(1) - .waitAverage(305.97) - .inVehicleTravelTimeMean(378.18) - .totalTravelTimeMean(684.16) - .build(); + .rejectionRate(0.0) + .rejections(1) + .waitAverage(305.97) + .inVehicleTravelTimeMean(378.18) + .totalTravelTimeMean(684.16) + .build(); verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats); } @@ -314,7 +340,7 @@ public void install() { * rejectionRate, rejections, waitAverage, inVehicleTravelTimeMean, & totalTravelTimeMean */ - private void verifyDrtCustomerStatsCloseToExpectedStats(String outputDirectory, Stats expectedStats) { + static void verifyDrtCustomerStatsCloseToExpectedStats( String outputDirectory, Stats expectedStats ) { String filename = outputDirectory + "/drt_customer_stats_drt.csv"; @@ -345,7 +371,7 @@ private void verifyDrtCustomerStatsCloseToExpectedStats(String outputDirectory, assertThat(actualStats).usingRecursiveComparison().isEqualTo(expectedStats); } - private static class Stats { + static class Stats { private final double rejectionRate; private final double rejections; private final double waitAverage; @@ -415,4 +441,5 @@ public Stats build() { } } } + } diff --git a/contribs/osm/src/main/java/org/matsim/contrib/osm/networkReader/LinkProperties.java b/contribs/osm/src/main/java/org/matsim/contrib/osm/networkReader/LinkProperties.java index 41d2b302d37..aa14def431b 100644 --- a/contribs/osm/src/main/java/org/matsim/contrib/osm/networkReader/LinkProperties.java +++ b/contribs/osm/src/main/java/org/matsim/contrib/osm/networkReader/LinkProperties.java @@ -26,6 +26,7 @@ public class LinkProperties { * @see #calculateSpeedIfSpeedTag(double) */ public static final double DEFAULT_FREESPEED_FACTOR = 0.9; + // This was calibrated by CR against OD travel times from here.com. Probably during day excluding rush hour. /** * Increase lane capacity for links shorter than this value, assuming they are crossing. diff --git a/examples/scenarios/mielec/mielec_drt_config.xml b/examples/scenarios/mielec/mielec_drt_config.xml index db2a8763ade..f6da9df061d 100644 --- a/examples/scenarios/mielec/mielec_drt_config.xml +++ b/examples/scenarios/mielec/mielec_drt_config.xml @@ -65,7 +65,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_edrt_config.xml b/examples/scenarios/mielec/mielec_edrt_config.xml index fdb3bd3c0f6..9f5b1eef218 100644 --- a/examples/scenarios/mielec/mielec_edrt_config.xml +++ b/examples/scenarios/mielec/mielec_edrt_config.xml @@ -82,7 +82,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_etaxi_benchmark_config.xml b/examples/scenarios/mielec/mielec_etaxi_benchmark_config.xml index 5102b37d246..38a0004109c 100644 --- a/examples/scenarios/mielec/mielec_etaxi_benchmark_config.xml +++ b/examples/scenarios/mielec/mielec_etaxi_benchmark_config.xml @@ -68,7 +68,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_etaxi_config.xml b/examples/scenarios/mielec/mielec_etaxi_config.xml index 59bfc20c581..6b11380d02a 100644 --- a/examples/scenarios/mielec/mielec_etaxi_config.xml +++ b/examples/scenarios/mielec/mielec_etaxi_config.xml @@ -74,7 +74,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_etaxi_config_assignment.xml b/examples/scenarios/mielec/mielec_etaxi_config_assignment.xml index b5df7dc9a9e..745fb5a4933 100644 --- a/examples/scenarios/mielec/mielec_etaxi_config_assignment.xml +++ b/examples/scenarios/mielec/mielec_etaxi_config_assignment.xml @@ -81,7 +81,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_serviceArea_based_drt_config.xml b/examples/scenarios/mielec/mielec_serviceArea_based_drt_config.xml index d2bdd9305f7..a43bc86595c 100644 --- a/examples/scenarios/mielec/mielec_serviceArea_based_drt_config.xml +++ b/examples/scenarios/mielec/mielec_serviceArea_based_drt_config.xml @@ -79,7 +79,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_stop_based_drt_config.xml b/examples/scenarios/mielec/mielec_stop_based_drt_config.xml index dd3df82fa60..00c0e17cf50 100644 --- a/examples/scenarios/mielec/mielec_stop_based_drt_config.xml +++ b/examples/scenarios/mielec/mielec_stop_based_drt_config.xml @@ -75,7 +75,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_taxi_benchmark_config.xml b/examples/scenarios/mielec/mielec_taxi_benchmark_config.xml index f94bca1d15e..a2ca777411d 100644 --- a/examples/scenarios/mielec/mielec_taxi_benchmark_config.xml +++ b/examples/scenarios/mielec/mielec_taxi_benchmark_config.xml @@ -58,7 +58,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_taxi_config.xml b/examples/scenarios/mielec/mielec_taxi_config.xml index 43389699cba..f37c7d92231 100644 --- a/examples/scenarios/mielec/mielec_taxi_config.xml +++ b/examples/scenarios/mielec/mielec_taxi_config.xml @@ -59,7 +59,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/mielec_taxi_mini_benchmark_config.xml b/examples/scenarios/mielec/mielec_taxi_mini_benchmark_config.xml index 8ab14beb61a..be6c84cd348 100644 --- a/examples/scenarios/mielec/mielec_taxi_mini_benchmark_config.xml +++ b/examples/scenarios/mielec/mielec_taxi_mini_benchmark_config.xml @@ -54,7 +54,9 @@ - - + + + + diff --git a/examples/scenarios/mielec/plans_only_drt_rejection_test.xml b/examples/scenarios/mielec/plans_only_drt_rejection_test.xml new file mode 100644 index 00000000000..d3a7b5cc06c --- /dev/null +++ b/examples/scenarios/mielec/plans_only_drt_rejection_test.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/scenarios/mielec/vehicles-2-cap-4.xml b/examples/scenarios/mielec/vehicles-2-cap-4.xml new file mode 100644 index 00000000000..776b28ba124 --- /dev/null +++ b/examples/scenarios/mielec/vehicles-2-cap-4.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/examples/scenarios/mielec/vehicles-rejection-test.xml b/examples/scenarios/mielec/vehicles-rejection-test.xml new file mode 100644 index 00000000000..f5de23d8f05 --- /dev/null +++ b/examples/scenarios/mielec/vehicles-rejection-test.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/matsim/src/main/java/org/matsim/api/core/v01/population/PopulationFactory.java b/matsim/src/main/java/org/matsim/api/core/v01/population/PopulationFactory.java index 16e81137b3c..f4648dcddf3 100644 --- a/matsim/src/main/java/org/matsim/api/core/v01/population/PopulationFactory.java +++ b/matsim/src/main/java/org/matsim/api/core/v01/population/PopulationFactory.java @@ -44,7 +44,10 @@ public interface PopulationFactory extends MatsimFactory { * It might in fact make sense to add a creational method that takes coord and link id. kai, aug'10 */ Activity createActivityFromCoord(String actType, Coord coord); - + + /** + * This presumably works for plans in the agent database, but does not seem to work for within-day replanning. kai, apr'23 + */ Activity createInteractionActivityFromCoord(String actType, Coord coord); /** diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/AbortHandler.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/AbortHandler.java new file mode 100644 index 00000000000..52105848b6d --- /dev/null +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/AbortHandler.java @@ -0,0 +1,9 @@ +package org.matsim.core.mobsim.qsim; + +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.qsim.components.QSimComponent; + +public interface AbortHandler extends QSimComponent { + boolean handleAbort( MobsimAgent abortHandler ); +} + diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/QSim.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/QSim.java index 6187c872dd6..489b3eb6b18 100755 --- a/matsim/src/main/java/org/matsim/core/mobsim/qsim/QSim.java +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/QSim.java @@ -124,6 +124,7 @@ public final class QSim implements VisMobsim, Netsim, ActivityEndRescheduler { private final MobsimListenerManager listenerManager; private final Scenario scenario; private final List activityHandlers = new ArrayList<>(); + private final List abortHandlers = new ArrayList<>(); private final List departureHandlers = new ArrayList<>(); private final org.matsim.core.mobsim.qsim.AgentCounter agentCounter; private final Map, MobsimAgent> agents = new LinkedHashMap<>(); @@ -272,8 +273,8 @@ public void run() { // we want to log this exception thrown during the cleanup, but not to throw it // since there is another (earlier) exception thrown in the try block log.warn("exception in finally block - " - + "this may be a follow-up exception of an exception thrown in the try block.", - cleanupException); + + "this may be a follow-up exception of an exception thrown in the try block.", + cleanupException); } else { // no exception thrown in the try block, let's re-throw the exception from the cleanup step throw cleanupException; @@ -294,8 +295,8 @@ public void run() { createAgents(); this.initSimTimer(); this.infoTime = Math.floor(this.simTimer.getSimStartTime() - / INFO_PERIOD) - * INFO_PERIOD; // infoTime may be < simStartTime, this ensures + / INFO_PERIOD) + * INFO_PERIOD; // infoTime may be < simStartTime, this ensures // to print out the info at the very first // timestep already @@ -322,7 +323,7 @@ public void addParkedVehicle(MobsimVehicle veh, Id startLinkId) { } else { if (wrnCnt2 < 1) { log.warn( "not able to add parked vehicle since there is no netsim engine. continuing anyway, but it may " - + "not be clear what this means ...") ; + + "not be clear what this means ...") ; log.warn(Gbl.ONLYONCE); wrnCnt2++; } @@ -456,33 +457,32 @@ public void insertAgentIntoMobsim(final MobsimAgent agent) { } private void arrangeNextAgentAction(final MobsimAgent agent) { - switch( agent.getState() ) { - case ACTIVITY: - arrangeAgentActivity(agent); - break ; - case LEG: - this.arrangeAgentDeparture(agent); - break ; - case ABORT: - this.events.processEvent( new PersonStuckEvent(this.simTimer.getTimeOfDay(), agent.getId(), agent.getCurrentLinkId(), agent.getMode())); - - // NOTE: in the same way as one can register departure handler or activity handler, we could allow to - // register abort handlers. If someone ever comes to this place here and needs this. kai, nov'17 - - this.agents.remove(agent.getId()) ; - this.agentCounter.decLiving(); - this.agentCounter.incLost(); - break ; - default: - throw new RuntimeException("agent with unknown state (possibly null)") ; - } - } - - private void arrangeAgentActivity(final MobsimAgent agent) { - for (ActivityHandler activityHandler : this.activityHandlers) { - if (activityHandler.handleActivity(agent)) { - return; + switch( agent.getState() ){ + case ACTIVITY -> { + for( ActivityHandler activityHandler : this.activityHandlers ){ + if( activityHandler.handleActivity( agent ) ){ + break; + } + } + } + case LEG -> this.arrangeAgentDeparture( agent ); + case ABORT -> { + for( AbortHandler abortHandler : this.abortHandlers ){ + if( abortHandler.handleAbort( agent ) ){ + return; + } + } + this.events.processEvent( new PersonStuckEvent( this.simTimer.getTimeOfDay(), agent.getId(), agent.getCurrentLinkId(), agent.getMode() ) ); + + // NOTE: in the same way as one can register departure handler or activity handler, we could allow to + // register abort handlers. If someone ever comes to this place here and needs this. kai, nov'17 + // The above comment has now been satisfied by the introduction of abort handlers. kai, mar'22 + + this.agents.remove( agent.getId() ); + this.agentCounter.decLiving(); + this.agentCounter.incLost(); } + default -> throw new RuntimeException( "agent with unknown state (possibly null)" ); } } @@ -561,13 +561,13 @@ private void printSimLog(final double time) { this.infoTime += INFO_PERIOD; Date endtime = new Date(); long diffreal = (endtime.getTime() - this.realWorldStarttime - .getTime()) / 1000; + .getTime()) / 1000; double diffsim = time - this.simTimer.getSimStartTime(); log.info("SIMULATION (NEW QSim) AT " + Time.writeTime(time) - + " : #Veh=" + this.agentCounter.getLiving() + " lost=" - + this.agentCounter.getLost() + " simT=" + diffsim - + "s realT=" + (diffreal) + "s; (s/r): " - + (diffsim / (diffreal + Double.MIN_VALUE))); + + " : #Veh=" + this.agentCounter.getLiving() + " lost=" + + this.agentCounter.getLost() + " simT=" + diffsim + + "s realT=" + (diffreal) + "s; (s/r): " + + (diffsim / (diffreal + Double.MIN_VALUE))); } } @@ -659,6 +659,11 @@ public void addActivityHandler(ActivityHandler activityHandler) { } } + public void addAbortHandler(AbortHandler abortHandler ) { + Gbl.assertNotNull( abortHandler ); + this.abortHandlers.add( abortHandler ); + } + /** * Adds the QueueSimulationListener instance given as parameters as listener * to this QueueSimulation instance. @@ -736,7 +741,7 @@ public final void addNetworkChangeEvent( NetworkChangeEvent event ) { } if ( !processed ) { throw new RuntimeException("received a network change event, but did not process it. Maybe " + - "the network change events engine was not set up for the qsim? Aborting ...") ; + "the network change events engine was not set up for the qsim? Aborting ...") ; } } diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/QSimProvider.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/QSimProvider.java index 5f11d96de97..15ce8449b83 100644 --- a/matsim/src/main/java/org/matsim/core/mobsim/qsim/QSimProvider.java +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/QSimProvider.java @@ -155,6 +155,11 @@ protected void configure() { log.info("Added MobsimListener " + instance.getClass()); } + if ( qSimComponent instanceof AbortHandler ) { + AbortHandler instance = (AbortHandler) qSimComponent; + qSim.addAbortHandler( instance ); + } + } } diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/components/QSimComponentsConfigGroup.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/components/QSimComponentsConfigGroup.java index 8878ebe4118..79704b119e6 100644 --- a/matsim/src/main/java/org/matsim/core/mobsim/qsim/components/QSimComponentsConfigGroup.java +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/components/QSimComponentsConfigGroup.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import org.matsim.core.config.ConfigGroup; +import org.matsim.core.config.ConfigUtils; import org.matsim.core.config.ReflectiveConfigGroup.StringGetter; import org.matsim.core.config.ReflectiveConfigGroup.StringSetter; import org.matsim.core.mobsim.qsim.ActivityEngineModule; @@ -71,6 +72,7 @@ public void setActiveComponents(List activeComponents) { this.activeComponents = new ArrayList<>( activeComponentsAsSet ) ; } + public void addActiveComponent( String component ) { // I need this so often that I am finally adding it here. kai, apr'23 diff --git a/matsim/src/main/java/org/matsim/core/population/PopulationUtils.java b/matsim/src/main/java/org/matsim/core/population/PopulationUtils.java index 9a572ead6ca..91c920da404 100644 --- a/matsim/src/main/java/org/matsim/core/population/PopulationUtils.java +++ b/matsim/src/main/java/org/matsim/core/population/PopulationUtils.java @@ -786,6 +786,9 @@ public static Activity createActivityFromLinkId(String type, Id linkId) { return getFactory().createActivityFromLinkId(type, linkId) ; } + /** + * This presumably works for plans in the agent database, but does not seem to work for within-day replanning. kai, apr'23 + */ public static Activity createInteractionActivityFromLinkId(String type, Id linkId) { return getFactory().createInteractionActivityFromLinkId(type, linkId) ; } @@ -794,6 +797,9 @@ public static Activity createActivityFromFacilityId(String type, Id facilityId) { return getFactory().createInteractionActivityFromActivityFacilityId(type, facilityId); } @@ -802,6 +808,9 @@ public static Activity createActivityFromCoord(String type, Coord coord) { return getFactory().createActivityFromCoord(type, coord) ; } + /** + * This presumably works for plans in the agent database, but does not seem to work for within-day replanning. kai, apr'23 + */ public static Activity createInteractionActivityFromCoord(String type, Coord coord) { return getFactory().createInteractionActivityFromCoord(type, coord) ; } @@ -812,6 +821,9 @@ public static Activity createActivityFromCoordAndLinkId(String type, Coord coord return act ; } + /** + * This presumably works for plans in the agent database, but does not seem to work for within-day replanning. kai, apr'23 + */ public static Activity createInteractionActivityFromCoordAndLinkId(String type, Coord coord, Id linkId) { Activity act = getFactory().createInteractionActivityFromCoord(type, coord) ; act.setLinkId(linkId); diff --git a/matsim/src/main/java/org/matsim/core/router/TripStructureUtils.java b/matsim/src/main/java/org/matsim/core/router/TripStructureUtils.java index 69ac6c3d0c3..049d3dae53f 100644 --- a/matsim/src/main/java/org/matsim/core/router/TripStructureUtils.java +++ b/matsim/src/main/java/org/matsim/core/router/TripStructureUtils.java @@ -385,12 +385,14 @@ public final static class Trip { private final List trip; private final List legs; - Trip( final Activity originActivity, - final List trip, + public Trip( final Activity originActivity, + final List planElements, final Activity destinationActivity) { + // (this is needed "public" quite often. Since there is no reason given why it should not be public, I am making it public now. kai, mar'23) + this.originActivity = originActivity; - this.trip = trip; - this.legs = extractLegs( trip ); + this.trip = planElements; + this.legs = extractLegs( planElements ); this.destinationActivity = destinationActivity; } diff --git a/matsim/src/main/java/org/matsim/withinday/utils/EditPlans.java b/matsim/src/main/java/org/matsim/withinday/utils/EditPlans.java index 9ffc19497e6..614373c521c 100644 --- a/matsim/src/main/java/org/matsim/withinday/utils/EditPlans.java +++ b/matsim/src/main/java/org/matsim/withinday/utils/EditPlans.java @@ -232,7 +232,6 @@ public void insertActivity(MobsimAgent agent, int index, Activity activity ) { String mode = TripStructureUtils.identifyMainMode( EditTrips.findCurrentTrip(agent ).getTripElements() ) ; insertActivity( agent, index, activity, mode, mode ) ; } - // === internal utility methods: === private void checkIfNotStageActivity(Activity origAct) { if( StageActivityTypeIdentifier.isStageActivity(origAct.getType()) ){ diff --git a/matsim/src/main/java/org/matsim/withinday/utils/EditTrips.java b/matsim/src/main/java/org/matsim/withinday/utils/EditTrips.java index 36f4776cbee..34d1fffcca3 100644 --- a/matsim/src/main/java/org/matsim/withinday/utils/EditTrips.java +++ b/matsim/src/main/java/org/matsim/withinday/utils/EditTrips.java @@ -42,6 +42,7 @@ import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.gbl.Gbl; import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.framework.PassengerAgent; import org.matsim.core.mobsim.qsim.AgentTracker; import org.matsim.core.mobsim.qsim.InternalInterface; import org.matsim.core.mobsim.qsim.agents.WithinDayAgentUtils; @@ -184,8 +185,10 @@ private void replanCurrentTripFromLeg(Activity newAct, final PlanElement current } else if ( currentLeg.getRoute() instanceof GenericRouteImpl ) { // teleported leg replanCurrentLegWithGenericRoute(newAct, routingMode, currentLeg, now, agent); + // yyyy ignores newAct and pulls the next activity from the plan, other than the other parallel methods for other route types. kai, mar'23 } else { - throw new ReplanningException("not implemented for the route type of the current leg") ; + log.warn("replanning not explicitly implemented for the route type of the current leg; falling back on generic method"); + replanFromCurrentLegWithUnknownRouteType( newAct, routingMode, currentLeg, now, agent, routingAttributes ); } WithinDayAgentUtils.resetCaches(agent); } @@ -222,6 +225,21 @@ private void replanCurrentLegWithNetworkRoute(Activity newAct, String mainMode, WithinDayAgentUtils.resetCaches(agent); } + + private void replanFromCurrentLegWithUnknownRouteType( Activity newAct, String mainMode, Leg currentLeg, double now, MobsimAgent agent, Attributes routingAttributes ) { + Plan plan = WithinDayAgentUtils.getModifiablePlan(agent) ; + List planElements = plan.getPlanElements() ; + Person person = plan.getPerson() ; + PassengerAgent ptPassengerAgent = (PassengerAgent) agent; + MobsimVehicle mobsimVehicle = ptPassengerAgent.getVehicle(); + if ( mobsimVehicle != null ) { + throw new ReplanningException( "agent has already boarded vehicle; don't know what to do." ); + } + + + } + + private void replanCurrentLegWithTransitRoute(Activity newAct, String routingMode, Leg currentLeg, double now, MobsimAgent agent, Attributes routingAttributes) { log.debug("entering replanCurrentLegWithTransitRoute for agentId=" + agent.getId()) ; @@ -240,38 +258,26 @@ private void replanCurrentLegWithTransitRoute(Activity newAct, String routingMod TransitPassengerRoute oldPtRoute = (TransitPassengerRoute) currentLeg.getRoute(); /* - * In AbstractTransitDriverAgent nextStop is moved forward only at departure, so - * while the vehicle stops "nextStop" is the stop where the vehicle stops at. - * (see AbstractTransitDriverAgent.depart() and - * AbstractTransitDriverAgent.processEventVehicleArrives()) - * - * So if the vehicle stops at a stop facility, driver.getNextTransitStop() will - * return that stop facility and we can replan the agent from that stop facility. - * gl may'19 - */ + * In AbstractTransitDriverAgent nextStop is moved forward only at departure, so while the vehicle stops "nextStop" is the stop where + * the vehicle stops at. (see AbstractTransitDriverAgent.depart() and AbstractTransitDriverAgent.processEventVehicleArrives()) + + * So if the vehicle stops at a stop facility, driver.getNextTransitStop() will return that stop facility and we can replan the agent + * from that stop facility. gl may'19 + + * TODO: Routing with the current vehicle as a start: If the agent is currently on a delayed bus, the router won't find that bus when + * looking for shortest paths from the next stop, because it assumes the bus has already departed. It will suggest to take another + * later bus even if staying on the current (delayed) bus is better. A real-time schedule based router would solve this apart from + * transfer times and disutilities (it won't take into account that staying on the same bus is one transfer less than getting off at + * the next stop and taking another bus from there). Otherwise one might route from all following stop facilities the delayed bus will + * still serve (high computation time, maybe less so with some special kind of many-to-one tree) or move only the corresponding + * departure in the schedule (high computation time for preparing the Raptor router's internal data structures, probably worst + * option). Not solving this will likely lead to change of routes without a proper reason / some kind of oscillation between two best + * or at least similarly good paths. + + * Idea 1: Use scheduled departure time instead of real current time to keep (delayed) bus the passenger is sitting on available from + * the router's perspective. Idea 2: Use input schedule file which has real (delayed) departure times as far as known at the time of + * withinday-replanning. - /* - * TODO: Routing with the current vehicle as a start: If the agent is currently - * on a delayed bus, the router won't find that bus when looking for shortest - * paths from the next stop, because it assumes the bus has already departed. It - * will suggest to take another later bus even if staying on the current - * (delayed) bus is better. A real-time schedule based router would solve this - * apart from transfer times and disutilities (it won't take into account that - * staying on the same bus is one transfer less than getting off at the next - * stop and taking another bus from there). Otherwise one might route from all - * following stop facilities the delayed bus will still serve (high computation - * time, maybe less so with some special kind of many-to-one tree) or move only - * the corresponding departure in the schedule (high computation time for - * preparing the Raptor router's internal data structures, probably worst - * option). Not solving this will likely lead to change of routes without a - * proper reason / some kind of oscillation between two best or at least - * similarly good paths. - * - * Idea 1: Use scheduled departure time instead of real current time to keep - * (delayed) bus the passenger is sitting on available from the router's - * perspective. Idea 2: Use input schedule file which has real (delayed) - * departure times as far as known at the time of withinday-replanning. - * * gl may'19 */ @@ -281,6 +287,8 @@ private void replanCurrentLegWithTransitRoute(Activity newAct, String routingMod List newTripElements = null; // (1) get new trip from current position to new activity: if (mobsimVehicle == null) { + // this is (I think) the situation where the agent is on the pt leg, but has not yet boarded a vehicle. kai, mar'23 + currentOrNextStop = scenario.getTransitSchedule().getFacilities().get(oldPtRoute.getAccessStopId()); log.debug( "agent with ID=" + agent.getId() + " is waiting at a stop=" + currentOrNextStop ) ; newTripElements = newTripToNewActivity(currentOrNextStop, newAct, routingMode, now, person, routingAttributes ); @@ -469,6 +477,7 @@ private void replanCurrentLegWithTransitRoute(Activity newAct, String routingMod * hypothetically something else than teleportation. */ private void replanCurrentLegWithGenericRoute(Activity newAct, String routingMode, Leg currentLeg, double now, MobsimAgent agent) { + // this pulls the next activity from the plan, other than the other parallel methods for other route types. kai, mar'23 Plan plan = WithinDayAgentUtils.getModifiablePlan(agent) ; @@ -604,12 +613,13 @@ private void replanCurrentTripFromStageActivity(Trip trip, int tripElementsIndex WithinDayAgentUtils.resetCaches(agent); } - public static boolean insertEmptyTrip( Plan plan, Activity fromActivity, Activity toActivity, String mainMode, PopulationFactory pf ) { + public static Trip insertEmptyTrip( Plan plan, Activity fromActivity, Activity toActivity, String mainMode, PopulationFactory pf ) { List list = Collections.singletonList( pf.createLeg( mainMode ) ) ; - TripRouter.insertTrip(plan, fromActivity, list, toActivity ) ; - return true ; + List planElements = TripRouter.insertTrip( plan, fromActivity, list, toActivity ); + TripStructureUtils.Trip trip = new Trip( fromActivity, planElements, toActivity ); + return trip; } - public final boolean insertEmptyTrip( Plan plan, Activity fromActivity, Activity toActivity, String mainMode ) { + public final Trip insertEmptyTrip( Plan plan, Activity fromActivity, Activity toActivity, String mainMode ) { return insertEmptyTrip( plan, fromActivity, toActivity, mainMode, this.pf ) ; } /**