From 52bcf00ea439b63350c764fc34a8b9f11bde0d25 Mon Sep 17 00:00:00 2001 From: Tarek Chouaki Date: Mon, 18 Nov 2024 12:58:39 +0100 Subject: [PATCH 1/5] feat: RaptorDeterminismTest --- .../ch/sbb/matsim/RaptorDeterminismTest.java | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 matsim/src/test/java/ch/sbb/matsim/RaptorDeterminismTest.java diff --git a/matsim/src/test/java/ch/sbb/matsim/RaptorDeterminismTest.java b/matsim/src/test/java/ch/sbb/matsim/RaptorDeterminismTest.java new file mode 100644 index 00000000000..99689ade903 --- /dev/null +++ b/matsim/src/test/java/ch/sbb/matsim/RaptorDeterminismTest.java @@ -0,0 +1,207 @@ +package ch.sbb.matsim; + +import ch.sbb.matsim.routing.pt.raptor.SwissRailRaptorData; +import ch.sbb.matsim.routing.pt.raptor.RaptorStopFinder; +import ch.sbb.matsim.routing.pt.raptor.RaptorParametersForPerson; +import ch.sbb.matsim.routing.pt.raptor.SwissRailRaptor; +import ch.sbb.matsim.routing.pt.raptor.RaptorParameters; +import ch.sbb.matsim.routing.pt.raptor.InitialStop; +import com.google.inject.Injector; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Test; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.population.*; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.events.EventsManagerModule; +import org.matsim.core.router.TripRouter; +import org.matsim.core.router.TripRouterModule; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.router.costcalculators.TravelDisutilityModule; +import org.matsim.core.scenario.ScenarioByInstanceModule; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.trafficmonitoring.TravelTimeCalculatorModule; +import org.matsim.core.utils.io.IOUtils; +import org.matsim.core.utils.timing.TimeInterpretationModule; +import org.matsim.examples.ExamplesUtils; +import org.matsim.facilities.ActivityFacilities; +import org.matsim.facilities.FacilitiesUtils; +import org.matsim.facilities.Facility; +import org.matsim.pt.routes.DefaultTransitPassengerRoute; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + + +import java.net.URL; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +public class RaptorDeterminismTest { + + public static boolean comparePlan(List left, List right) { + if(left.size() != right.size()) { + return false; + } + for(int i=0; i[] transitStopFacilitiesByScenario = new List[scenarioSamples]; + List[] personLists = new List[scenarioSamples]; + TripRouter[] tripRouters = new TripRouter[scenarioSamples]; + + logger.info(String.format("Loading scenario %d times", scenarioSamples)); + for(int scenarioIndex=0; scenarioIndex(scenario.getTransitSchedule().getFacilities().values()); + swissRailRaptorData[scenarioIndex] = swissRailRaptors[scenarioIndex].getUnderlyingData(); + personLists[scenarioIndex] = scenario.getPopulation().getPersons().values().stream().toList(); + + tripRouters[scenarioIndex] = injector.getInstance(TripRouter.class); + } + + logger.info(String.format("Comparing stop facilities order %d", scenarioSamples)); + + for(int scenarioIndex=1; scenarioIndex referenceElements = tripRouters[0].calcRoute("pt", fromFacility, toFacility, referenceTrip.getOriginActivity().getEndTime().seconds(), referencePerson, referenceTrip.getTripAttributes()); + + for(int scenarioIndex=1; scenarioIndex referenceInitialStops = raptorStopFinders[0].findStops(fromFacility, toFacility, referencePerson, referenceTrip.getOriginActivity().getEndTime().seconds(), referenceTrip.getTripAttributes(), referenceRaptorParameters, swissRailRaptorData[0], direction); + List sortedReferenceInitialStops = new ArrayList<>(referenceInitialStops); + sortedReferenceInitialStops.sort(Comparator.comparing(InitialStop::toString)); + + List comparedInitialStops = raptorStopFinders[scenarioIndex].findStops(otherFromFacility, otherToFacility, referencePerson, referenceTrip.getOriginActivity().getEndTime().seconds(), referenceTrip.getTripAttributes(), otherRaptorParameters, swissRailRaptorData[scenarioIndex], direction); + + assert referenceInitialStops.size() == comparedInitialStops.size(); + + List sortedComparedInitialStops = new ArrayList<>(comparedInitialStops); + sortedComparedInitialStops.sort(Comparator.comparing(InitialStop::toString)); + for(int j=0; j comparedElements = tripRouters[scenarioIndex].calcRoute("pt", otherFromFacility, otherToFacility, referenceTrip.getOriginActivity().getEndTime().seconds(), otherPerson, referenceTrip.getTripAttributes()); + assert comparePlan(referenceElements, comparedElements); + } + } + } + } + +} From 4419c329ed6e0814a330e9fbee45eafd35063b2c Mon Sep 17 00:00:00 2001 From: Tarek Chouaki Date: Mon, 18 Nov 2024 12:59:56 +0100 Subject: [PATCH 2/5] feat: DeterminismTest --- .../discrete_mode_choice/DeterminismTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 contribs/discrete_mode_choice/src/test/java/org/matsim/contrib/discrete_mode_choice/DeterminismTest.java diff --git a/contribs/discrete_mode_choice/src/test/java/org/matsim/contrib/discrete_mode_choice/DeterminismTest.java b/contribs/discrete_mode_choice/src/test/java/org/matsim/contrib/discrete_mode_choice/DeterminismTest.java new file mode 100644 index 00000000000..bebc456de8f --- /dev/null +++ b/contribs/discrete_mode_choice/src/test/java/org/matsim/contrib/discrete_mode_choice/DeterminismTest.java @@ -0,0 +1,55 @@ +package org.matsim.contrib.discrete_mode_choice; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Test; +import org.matsim.api.core.v01.Scenario; +import org.matsim.contribs.discrete_mode_choice.modules.DiscreteModeChoiceModule; +import org.matsim.contribs.discrete_mode_choice.modules.ModeAvailabilityModule; +import org.matsim.contribs.discrete_mode_choice.modules.config.DiscreteModeChoiceConfigGroup; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.gbl.MatsimRandom; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.utils.io.IOUtils; +import org.matsim.core.utils.misc.CRCChecksum; +import org.matsim.examples.ExamplesUtils; + +import java.net.URL; + +public class DeterminismTest { + private static void runConfig(URL configUrl, String outputDirectory) { + Config config = ConfigUtils.loadConfig(configUrl, new DiscreteModeChoiceConfigGroup()); + config.controller().setLastIteration(2); + config.controller().setOutputDirectory(outputDirectory); + config.controller().setOverwriteFileSetting(OutputDirectoryHierarchy.OverwriteFileSetting.deleteDirectoryIfExists); + Scenario scenario = ScenarioUtils.createScenario(config); + ScenarioUtils.loadScenario(scenario); + Controler controler = new Controler(scenario); + controler.addOverridingModule(new DiscreteModeChoiceModule()); + controler.addOverridingModule(new ModeAvailabilityModule()); + controler.run(); + } + + + @Test + public void testSimulationDeterminism() { + Logger logger = LogManager.getLogger(DeterminismTest.class); + logger.info("Testing simulation determinism"); + URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("siouxfalls-2014"), "config_default.xml"); + int samples = 10; + for(int i=0; i Date: Mon, 18 Nov 2024 13:07:20 +0100 Subject: [PATCH 3/5] fix: deterministic creation of stops quad tree --- .../routing/pt/raptor/SwissRailRaptorData.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java index a02b58c7b35..4638508080c 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java @@ -20,17 +20,7 @@ package ch.sbb.matsim.routing.pt.raptor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Supplier; import javax.annotation.Nullable; @@ -148,7 +138,7 @@ public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Veh // enumerate TransitStopFacilities along their usage in transit routes to (hopefully) achieve a better memory locality // well, I'm not even sure how often we'll need the transit stop facilities, likely we'll use RouteStops more often Map stopFacilityIndices = new HashMap<>((int) (schedule.getFacilities().size() * 1.5)); - Map routeStopsPerStopFacility = new HashMap<>(); + Map routeStopsPerStopFacility = new LinkedHashMap<>(); boolean useModeMapping = staticConfig.isUseModeMappingForPassengers(); for (TransitLine line : schedule.getTransitLines().values()) { From dbcff75e5dda943f1bb75432b7a1555a98d8ab11 Mon Sep 17 00:00:00 2001 From: Tarek Chouaki Date: Mon, 18 Nov 2024 13:18:32 +0100 Subject: [PATCH 4/5] fix: more deterministic SwissRailRaptorCore.calcLeastCostRoute --- .../routing/pt/raptor/SwissRailRaptorCore.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java index d6c059897d8..2ee81149804 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java @@ -38,15 +38,7 @@ import org.matsim.pt.transitSchedule.api.TransitStopFacility; import org.matsim.vehicles.Vehicle; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; +import java.util.*; /** * The actual RAPTOR implementation, based on Delling et al, Round-Based Public Transit Routing. @@ -118,7 +110,7 @@ public RaptorRoute calcLeastCostRoute(double depTime, Facility fromFacility, Fac reset(); CachingTransferProvider transferProvider = this.data.new CachingTransferProvider(); - Map destinationStops = new HashMap<>(); + Map destinationStops = new LinkedHashMap<>(); // go through all egressStops; check if already in destinationStops; if so, check if current cost is smaller; if so, then replace. This can // presumably happen when the same stop can be reached at lower cost by a different egress mode. (*) @@ -141,7 +133,7 @@ public RaptorRoute calcLeastCostRoute(double depTime, Facility fromFacility, Fac } // same as (*) for access stops: - Map initialStops = new HashMap<>(); + Map initialStops = new LinkedHashMap<>(); for (InitialStop accessStop : accessStops) { InitialStop alternative = initialStops.get(accessStop.stop); if (alternative == null || accessStop.accessCost < alternative.accessCost) { @@ -833,7 +825,7 @@ private void handleTransfers(boolean strict, RaptorParameters raptorParams, Cach final int firstTransferIndex; final int lastTransferIndex; final RTransfer[] transfers; - + if (!useAdaptiveTransferCalculation) { // efficient lookup from the precomputed transfer candidates transfers = this.data.transfers; From ef5aa79de15a0d47368299215510110ba52a785e Mon Sep 17 00:00:00 2001 From: Tarek Chouaki Date: Tue, 19 Nov 2024 09:24:28 +0100 Subject: [PATCH 5/5] chore: code commenting --- .../ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java | 2 ++ .../ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java index 2ee81149804..521c237d412 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java @@ -110,6 +110,7 @@ public RaptorRoute calcLeastCostRoute(double depTime, Facility fromFacility, Fac reset(); CachingTransferProvider transferProvider = this.data.new CachingTransferProvider(); + // Using a LinkedHashMap instead of a regular HashMap here is necessary to have a deterministic behaviour Map destinationStops = new LinkedHashMap<>(); // go through all egressStops; check if already in destinationStops; if so, check if current cost is smaller; if so, then replace. This can @@ -133,6 +134,7 @@ public RaptorRoute calcLeastCostRoute(double depTime, Facility fromFacility, Fac } // same as (*) for access stops: + // Also, using a LinkedHashMap instead of a regular HashMap here is necessary to have a deterministic behaviour Map initialStops = new LinkedHashMap<>(); for (InitialStop accessStop : accessStops) { InitialStop alternative = initialStops.get(accessStop.stop); diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java index 4638508080c..e4d4f28a1c5 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java @@ -138,7 +138,8 @@ public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Veh // enumerate TransitStopFacilities along their usage in transit routes to (hopefully) achieve a better memory locality // well, I'm not even sure how often we'll need the transit stop facilities, likely we'll use RouteStops more often Map stopFacilityIndices = new HashMap<>((int) (schedule.getFacilities().size() * 1.5)); - Map routeStopsPerStopFacility = new LinkedHashMap<>(); + // Using a LinkedHashMap instead of a regular HashMap here is necessary to have a deterministic behaviour + Map routeStopsPerStopFacility = new LinkedHashMap<>(); boolean useModeMapping = staticConfig.isUseModeMappingForPassengers(); for (TransitLine line : schedule.getTransitLines().values()) {