Repository: r5py/r5py (language: python)

Issue:
Travel times from/to the same ID is sometimes non-zero
**Describe the bug**
When computing travel time matrices using `compute_travel_times()`  on a `TravelTimeMatrixComupter()` object, the travel times from/to the same ID are sometimes non-zero.

**To Reproduce**
Steps to reproduce the behavior:
1. Collect input pbf/GTFS data for the [Helsinki demo](https://github.com/r5py/r5py/tree/main/docs/data/Helsinki).
2. Follow the usage example in the [r5py docs](https://r5py.readthedocs.io/en/stable/notebooks/basic-usage.html), in particular [this section](https://r5py.readthedocs.io/en/stable/notebooks/basic-usage.html#compute-travel-time-matrix-from-one-to-all-locations) which calculates the travel times to/from all points.
3. Look for rows where the `from_id` and `to_id` are the same and `travel_time` is non-zero, e.g.
```
travel_time_matrix[(travel_time_matrix.from_id == travel_time_matrix.to_id) & (travel_time_matrix.travel_time != 0)]
```
4. Some rows are then returned where the start/end points are the same, but the travel times are non-zero.

**Expected behavior**
Travel times to/from the same point would always be 0.

**Environment:**
- OS: macOS Monterey v12.6
- Python package source: pypi/pip
- Python version: 3.9.16
- Java: AdoptOpenJDK 11.0.11+9

**Additional context**
I have tried an alternative .pbf/GTFS source and this observation remains. I have also tried various combinations of transit/walk and the issue is also present there too. Finally, as a preprocessing step, I extracted the node coordinates from the OSM file using `osmium` and I have snapped my origin/destination points to the closest OSM node and then re-run. This behaviour remained present in this case.

**Test data and/or script/notebook**
This behaviour was first observed using the [Helsinki demo](https://github.com/r5py/r5py/tree/main/docs/data/Helsinki) and code written in the [r5py docs usage page](https://r5py.readthedocs.io/en/stable/notebooks/basic-usage.html).

Travel times are non-replicable
**Describe the bug**
Given the same input data (origins/destinations, OSM, and GTFS), and the same parameters, r5py will return travel-time matrices with very significant differences. This seems to happen whether or not I rebuild the network or regenerate the computer in between runs. Most O/D pairs are the same, but a small fraction of pairs are substantially different - off by 20% or more of the travel time, often tens of minutes, enough to raise serious doubts concerning the quality of the results. 

I'm not an expert, it's very likely that I've made some mistake in configuring my inputs or my parameters, but I've been digging through the r5py documentation and trying everything I can think of, and I can't get r5py to give replicable results. Help me find what I'm doing wrong?

**To Reproduce**
0. I've tested both the development version of r5py and the stable release, they seem to have the same issue.
1. Use this [Google Drive](https://drive.google.com/drive/folders/1RsCRm7ejjTZ9kshMO7sg3_G5qbhwBFsh?usp=sharing) to download the data (scenarios/existing_conditions/) and the script (routing.py) that I'm using to generate travel_time matrices.
2. Run routing.route_scenario('scenarios/existing_conditions/'), and move the resulting files out of the results/ directory.
3. Run routing.route_scenario('scenarios/existing_conditions/') again, and compare the results to the first run. 

**Expected behavior**
For my intended use in transportation planning, I need a routing engine that can be configured to return identical, deterministic results every time an analysis is run on the same input data with the same parameters.

**Environment:**

    OS: Ubuntu 20.04.4 LTS
    Python package source: conda
    Versions of Python, Java Development Kit, Python modules
    Python 3.9
    javac 11.0.16 (I think, from javac -version, I'm not certain how to check within conda/python)




Discussion / hints:

Hi!

Yes, we are aware of a certain non-deterministic component in the routes. It seems to be a design choice of the underlying R5 engine (https://github.com/conveyal/r5), and we currently have not decided whether we want to try to handle this any different. 

In our test data sets, we found that, often, it is the access or egress from an origin point or to a destination point that is causing the most random contribution to travel time. If you want to eliminate this, snap the origin and destination to the closest point on the street network. We are working on a helper function to achieve exactly this, but it’s still taking a bit.
see also: https://github.com/r5py/r5py/issues/224
Hi,

I have also been experiencing travel times that are non-replicable. This is with both the [Helsinki demo data](https://github.com/r5py/r5py/tree/main/docs/data/Helsinki) and an alternative .pbf/GTFS source I have been using with OTP that gives deterministic results there. I notice from the Conway, Byrd and van de Linden 2017 paper there is a non-deterministic/Monte Carlo aspect to r5's routing algo when the GTFS/transit data is frequency-based.  The Helsinki GTFS data does have a `frequencies.txt` file, but since this file is optional in the GTFS spec I have re-run with this file removed and the results remain non-deterministic. The other GTFS data source I have been using is time-table based (has no frequencies.txt file) and gives non-deterministic results too.

To explore the 'snapping' , as a pre-processing step I extracted the node coordinates from the OSM file using `osmium` and I have snapped my origin/destination points to the closest OSM node and then re-run. The results were also non-deterministic in this case.

I have also tried various combinations of transit modes/walking, building the network with and without GTFS data, but I'm not yet able to get repeatable results. Any help/advice would be greatly appreciated, and if I can assist with anything just let me know.

Environment:
- OS: macOS Monterey v12.6
- Python package source: pypi/pip
- Python version: 3.9.16
- Java: AdoptOpenJDK 11.0.11+9

Fix patch:
diff --git a/docs/md/basic-usage.md b/docs/md/basic-usage.md
index bfba7612..197af54d 100644
--- a/docs/md/basic-usage.md
+++ b/docs/md/basic-usage.md
@@ -5,7 +5,7 @@ jupyter:
       extension: .md
       format_name: markdown
       format_version: '1.3'
-      jupytext_version: 1.14.0
+      jupytext_version: 1.14.4
   kernelspec:
     display_name: Python 3 (ipykernel)
     language: python
@@ -100,7 +100,8 @@ Let's start by downloading a sample point dataset into a geopandas `GeoDataFrame
 ```python
 import geopandas 
 
-points_url = "https://github.com/r5py/r5py/raw/main/docs/data/Helsinki/population_points_2020.gpkg"
+# temporary fix, this is not getting published as such, anyway, but only after PR #238 is merged
+points_url = "https://github.com/r5py/r5py/raw/stable/docs/data/Helsinki/population_points_2020.gpkg"
 points = geopandas.read_file(points_url)
 points.head()
 ```
diff --git a/docs/notebooks/basic-usage.ipynb b/docs/notebooks/basic-usage.ipynb
index 2b0b7379..b1e9b8b3 100644
--- a/docs/notebooks/basic-usage.ipynb
+++ b/docs/notebooks/basic-usage.ipynb
@@ -133,7 +133,8 @@
    "source": [
     "import geopandas\n",
     "\n",
-    "points_url = \"https://github.com/r5py/r5py/raw/main/docs/data/Helsinki/population_points_2020.gpkg\"\n",
+    "# temporary fix, this is not getting published as such, anyway, but only after PR #238 is merged\n",
+    "points_url = \"https://github.com/r5py/r5py/raw/stable/docs/data/Helsinki/population_points_2020.gpkg\"\n",
     "points = geopandas.read_file(points_url)\n",
     "points.head()"
    ]
@@ -233,10 +234,7 @@
     "from r5py import TransportNetwork\n",
     "\n",
     "transport_network = TransportNetwork(\n",
-    "    f\"{DATA_DIR}/Helsinki/kantakaupunki.osm.pbf\",\n",
-    "    [\n",
-    "        f\"{DATA_DIR}/Helsinki/GTFS.zip\",\n",
-    "    ],\n",
+    "    f\"{DATA_DIR}/Helsinki/kantakaupunki.osm.pbf\", [f\"{DATA_DIR}/Helsinki/GTFS.zip\"]\n",
     ")"
    ]
   },
@@ -286,10 +284,7 @@
     "    origins=origin,\n",
     "    destinations=points,\n",
     "    departure=datetime.datetime(2022, 2, 22, 8, 30),\n",
-    "    transport_modes=[\n",
-    "        TransitMode.TRANSIT,\n",
-    "        LegMode.WALK,\n",
-    "    ],\n",
+    "    transport_modes=[TransitMode.TRANSIT, LegMode.WALK],\n",
     ")"
    ]
   },
@@ -592,7 +587,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.7"
+   "version": "3.10.9"
   }
  },
  "nbformat": 4,


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 2937d144..be013505 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -54,7 +54,7 @@
 
 
 @pytest.fixture(scope="session")
-def blank_regional_task(population_grid_points):
+def regional_task(population_grid_points):
     import r5py
 
     transport_network = r5py.TransportNetwork(OSM_PBF, [GTFS])
@@ -85,6 +85,16 @@ def data_columns_with_breakdown(scope="session"):
     ]
 
 
+@pytest.fixture()
+def gtfs_file():
+    yield GTFS
+
+
+@pytest.fixture()
+def not_a_gtfs_file():
+    yield OSM_PBF
+
+
 @pytest.fixture
 def gtfs_timezone_helsinki():
     yield "Europe/Helsinki"
diff --git a/tests/test_contains_gtfs_data.py b/tests/test_contains_gtfs_data.py
index 512e56e4..23b311a9 100644
--- a/tests/test_contains_gtfs_data.py
+++ b/tests/test_contains_gtfs_data.py
@@ -1,28 +1,21 @@
 #!/usr/bin/env python3
 
 
-import pathlib
-
 import pytest  # noqa: F401
 
 import r5py
 
-DATA_DIRECTORY = pathlib.Path(__file__).absolute().parents[1] / "docs/data"
-GTFS_FILE = DATA_DIRECTORY / "Helsinki" / "GTFS.zip"
-NOT_GTFS_FILE = DATA_DIRECTORY / "Helsinki" / "kantakaupunki.osm.pbf"
-
 
 class TestContainsGtfsData:
     @pytest.mark.parametrize(
         ["path", "expected"],
         [
-            (GTFS_FILE, True),
-            (str(GTFS_FILE), True),
-            (open(GTFS_FILE, "rb"), True),
-            (NOT_GTFS_FILE, False),
-            (str(NOT_GTFS_FILE), False),
-            (open(NOT_GTFS_FILE, "rb"), False),
+            (pytest.lazy_fixture("gtfs_file"), True),
+            (pytest.lazy_fixture("not_a_gtfs_file"), False),
         ],
     )
     def test_contains_gtfs_data(self, path, expected):
         assert r5py.util.contains_gtfs_data(path) == expected
+        assert r5py.util.contains_gtfs_data(str(path)) == expected
+        with open(path, "rb") as f:
+            r5py.util.contains_gtfs_data(f)
diff --git a/tests/test_deterministic_behaviour.py b/tests/test_deterministic_behaviour.py
new file mode 100644
index 00000000..9a1733c1
--- /dev/null
+++ b/tests/test_deterministic_behaviour.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+
+
+"""Test whether routing is (more or less) deterministic (https://github.com/r5py/r5py/issues/240)."""
+
+
+import itertools
+
+import pytest  # noqa: F401
+
+import r5py
+
+
+@pytest.fixture(scope="module")
+def intermediate_results():
+    # will be modified in each iteration,
+    # used to compare results across computation runs
+    yield []
+
+
+# Python 3.10+
+def pairwise(iterable):
+    """pairwise("ABCDEFG") --> AB BC CD DE EF FG ."""
+    try:
+        return itertools.pairwise(iterable)
+    except AttributeError:  # Python<3.10
+        a, b = itertools.tee(iterable)
+        next(b, None)
+        return zip(a, b)
+
+
+class TestDeterministicBehaviour:
+    @pytest.mark.parametrize("iteration", range(10))
+    def test_public_transport_travel_time_matrices(
+        self, transport_network, population_grid_points, intermediate_results, iteration
+    ):
+        travel_time_matrix_computer = r5py.TravelTimeMatrixComputer(
+            transport_network,
+            population_grid_points,
+        )
+        travel_times = travel_time_matrix_computer.compute_travel_times()
+
+        intermediate_results.append(travel_times)
+
+        for matrix_a, matrix_b in pairwise(intermediate_results):
+            assert (
+                matrix_a.set_index(["from_id", "to_id"])
+                .sort_index()
+                .equals(matrix_b.set_index(["from_id", "to_id"]).sort_index())
+            )
diff --git a/tests/test_regional_task.py b/tests/test_regional_task.py
index d8c7f5c5..e5e4e60f 100644
--- a/tests/test_regional_task.py
+++ b/tests/test_regional_task.py
@@ -18,31 +18,25 @@
 
 class TestRegionalTask:
     @pytest.mark.parametrize(
-        ["regional_task", "access_modes", "expected"],
+        ["access_modes", "expected"],
         [
-            (pytest.lazy_fixture("blank_regional_task"), [], set([])),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 [r5py.LegMode.WALK],
                 set([r5py.LegMode.WALK]),
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 [r5py.LegMode.BICYCLE],
                 set([r5py.LegMode.BICYCLE]),
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 [r5py.LegMode.CAR],
                 set([r5py.LegMode.CAR]),
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 [r5py.LegMode.WALK, r5py.LegMode.BICYCLE],
                 set([r5py.LegMode.WALK, r5py.LegMode.BICYCLE]),
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 [r5py.LegMode.WALK, r5py.LegMode.BICYCLE, r5py.LegMode.CAR],
                 set([r5py.LegMode.WALK, r5py.LegMode.BICYCLE, r5py.LegMode.CAR]),
             ),
@@ -57,7 +51,6 @@ def test_access_mode_setter(self, regional_task, access_modes, expected):
 
     @pytest.mark.parametrize(
         [
-            "regional_task",
             "departure",
             "expected",
             "expected_java_date",
@@ -65,7 +58,6 @@ def test_access_mode_setter(self, regional_task, access_modes, expected):
         ],
         [
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.datetime(2022, 2, 22, 8, 30),
                 datetime.datetime(2022, 2, 22, 8, 30),
                 java.time.LocalDate.of(2022, 2, 22),
@@ -87,12 +79,12 @@ def test_departure_setter(
         assert regional_task._regional_task.fromTime == expected_java_from_time
 
     @pytest.mark.parametrize(
-        ["regional_task", "bicycle_stress", "expected"],
+        ["bicycle_stress", "expected"],
         [
-            (pytest.lazy_fixture("blank_regional_task"), 1, 1),
-            (pytest.lazy_fixture("blank_regional_task"), 2, 2),
-            (pytest.lazy_fixture("blank_regional_task"), 3, 3),
-            (pytest.lazy_fixture("blank_regional_task"), 4, 4),
+            (1, 1),
+            (2, 2),
+            (3, 3),
+            (4, 4),
         ],
     )
     def test_max_bicycle_traffic_stress_setter(
@@ -103,12 +95,12 @@ def test_max_bicycle_traffic_stress_setter(
         assert regional_task._regional_task.bikeTrafficStress == expected
 
     @pytest.mark.parametrize(
-        ["regional_task", "max_pt_rides", "expected"],
+        ["max_pt_rides", "expected"],
         [
-            (pytest.lazy_fixture("blank_regional_task"), 1, 1),
-            (pytest.lazy_fixture("blank_regional_task"), 1232, 1232),
-            (pytest.lazy_fixture("blank_regional_task"), 31, 31),
-            (pytest.lazy_fixture("blank_regional_task"), 999, 999),
+            (1, 1),
+            (1232, 1232),
+            (31, 31),
+            (999, 999),
         ],
     )
     def test_max_public_transport_rides_setter(
@@ -119,22 +111,19 @@ def test_max_public_transport_rides_setter(
         assert regional_task._regional_task.maxRides == expected
 
     @pytest.mark.parametrize(
-        ["regional_task", "max_time", "expected", "expected_java"],
+        ["max_time", "expected", "expected_java"],
         [
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(hours=1),
                 datetime.timedelta(hours=1),
                 60,
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(minutes=15),
                 datetime.timedelta(minutes=15),
                 15,
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(hours=7, minutes=23),
                 datetime.timedelta(hours=7, minutes=23),
                 443,
@@ -149,22 +138,19 @@ def test_max_time_setter(self, regional_task, max_time, expected, expected_java)
         assert regional_task._regional_task.maxCarTime == expected_java
 
     @pytest.mark.parametrize(
-        ["regional_task", "max_time_cycling", "expected", "expected_java"],
+        ["max_time_cycling", "expected", "expected_java"],
         [
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(hours=1),
                 datetime.timedelta(hours=1),
                 60,
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(minutes=15),
                 datetime.timedelta(minutes=15),
                 15,
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(hours=7, minutes=23),
                 datetime.timedelta(hours=7, minutes=23),
                 443,
@@ -179,22 +165,19 @@ def test_max_time_cycling_setter(
         assert regional_task._regional_task.maxBikeTime == expected_java
 
     @pytest.mark.parametrize(
-        ["regional_task", "max_time_driving", "expected", "expected_java"],
+        ["max_time_driving", "expected", "expected_java"],
         [
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(hours=1),
                 datetime.timedelta(hours=1),
                 60,
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(minutes=15),
                 datetime.timedelta(minutes=15),
                 15,
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(hours=7, minutes=23),
                 datetime.timedelta(hours=7, minutes=23),
                 443,
@@ -209,22 +192,19 @@ def test_max_time_driving_setter(
         assert regional_task._regional_task.maxCarTime == expected_java
 
     @pytest.mark.parametrize(
-        ["regional_task", "max_time_walking", "expected", "expected_java"],
+        ["max_time_walking", "expected", "expected_java"],
         [
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(hours=1),
                 datetime.timedelta(hours=1),
                 60,
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(minutes=15),
                 datetime.timedelta(minutes=15),
                 15,
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 datetime.timedelta(hours=7, minutes=23),
                 datetime.timedelta(hours=7, minutes=23),
                 443,
@@ -239,28 +219,26 @@ def test_max_time_walking_setter(
         assert regional_task._regional_task.maxWalkTime == expected_java
 
     @pytest.mark.parametrize(
-        ["regional_task", "percentiles"],
+        ["percentiles"],
         [
-            (pytest.lazy_fixture("blank_regional_task"), []),
-            (pytest.lazy_fixture("blank_regional_task"), [50]),
-            (pytest.lazy_fixture("blank_regional_task"), [33, 66]),
-            (pytest.lazy_fixture("blank_regional_task"), [25, 50, 75]),
-            (pytest.lazy_fixture("blank_regional_task"), [20, 40, 60, 80]),
-            (pytest.lazy_fixture("blank_regional_task"), [16, 33, 50, 66, 83]),
+            ([],),
+            ([50],),
+            ([33, 66],),
+            ([25, 50, 75],),
+            ([20, 40, 60, 80],),
+            ([16, 33, 50, 66, 83],),
         ],
     )
     def test_allowed_number_of_percentiles(self, regional_task, percentiles):
         regional_task.percentiles = percentiles
 
     @pytest.mark.parametrize(
-        ["regional_task", "percentiles"],
+        ["percentiles"],
         [
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 [10, 20, 30, 40, 50, 60, 70, 90],
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 [i for i in range(101)],
             ),
         ],
@@ -271,9 +249,6 @@ def test_out_of_range_percentiles(self, regional_task, percentiles):
         ):
             regional_task.percentiles = percentiles
 
-    @pytest.mark.parametrize(
-        ["regional_task"], [(pytest.lazy_fixture("blank_regional_task"),)]
-    )
     def test_scenario(self, regional_task):
         from r5py.r5 import Scenario
 
@@ -283,13 +258,13 @@ def test_scenario(self, regional_task):
         assert regional_task._regional_task.scenarioId == scenario.id
 
     @pytest.mark.parametrize(
-        ["regional_task", "speed_cycling", "expected", "expected_java"],
+        ["speed_cycling", "expected", "expected_java"],
         [
-            (pytest.lazy_fixture("blank_regional_task"), 14.4, 14.4, 4.0),
-            (pytest.lazy_fixture("blank_regional_task"), 18, 18.0, 5.0),
-            (pytest.lazy_fixture("blank_regional_task"), 19.8, 19.8, 5.5),
-            (pytest.lazy_fixture("blank_regional_task"), 28.8, 28.8, 8.0),
-            (pytest.lazy_fixture("blank_regional_task"), 36, 36, 10.0),
+            (14.4, 14.4, 4.0),
+            (18, 18.0, 5.0),
+            (19.8, 19.8, 5.5),
+            (28.8, 28.8, 8.0),
+            (36, 36, 10.0),
         ],
     )
     def test_speed_cycling_setter(
@@ -300,12 +275,12 @@ def test_speed_cycling_setter(
         assert regional_task._regional_task.bikeSpeed == pytest.approx(expected_java)
 
     @pytest.mark.parametrize(
-        ["regional_task", "speed_walking", "expected", "expected_java"],
+        ["speed_walking", "expected", "expected_java"],
         [
-            (pytest.lazy_fixture("blank_regional_task"), 3.6, 3.6, 1.0),
-            (pytest.lazy_fixture("blank_regional_task"), 5.4, 5.4, 1.5),
-            (pytest.lazy_fixture("blank_regional_task"), 7.2, 7.2, 2.0),
-            (pytest.lazy_fixture("blank_regional_task"), 9.0, 9.0, 2.5),
+            (3.6, 3.6, 1.0),
+            (5.4, 5.4, 1.5),
+            (7.2, 7.2, 2.0),
+            (9.0, 9.0, 2.5),
         ],
     )
     def test_speed_walking_setter(
@@ -316,10 +291,10 @@ def test_speed_walking_setter(
         assert regional_task._regional_task.walkSpeed == pytest.approx(expected_java)
 
     @pytest.mark.parametrize(
-        ["regional_task", "breakdown"],
+        ["breakdown"],
         [
-            (pytest.lazy_fixture("blank_regional_task"), True),
-            (pytest.lazy_fixture("blank_regional_task"), False),
+            (True,),
+            (False,),
         ],
     )
     def test_breakdown_setter_getter(self, regional_task, breakdown):
@@ -327,14 +302,12 @@ def test_breakdown_setter_getter(self, regional_task, breakdown):
         assert regional_task.breakdown == breakdown
 
     @pytest.mark.parametrize(
-        ["regional_task", "origin"],
+        ["origin"],
         [
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 shapely.geometry.Point(60, 24),
             ),
             (
-                pytest.lazy_fixture("blank_regional_task"),
                 shapely.geometry.Point(61, 25),
             ),
         ],
diff --git a/tests/test_street_layer.py b/tests/test_street_layer.py
index bc29c9f2..c8b0c603 100644
--- a/tests/test_street_layer.py
+++ b/tests/test_street_layer.py
@@ -8,136 +8,111 @@
 
 class Test_StreetLayer:
     @pytest.mark.parametrize(
-        ["transport_network_", "point", "snapped_point"],
+        ["point", "snapped_point"],
         [
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.934716339546334, 60.162003377483465),
                 shapely.Point(24.9344161, 60.1620914),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.93472078921679, 60.159759479145855),
                 shapely.Point(24.935045, 60.1598117),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.934725238091787, 60.157515624929495),
                 shapely.Point(24.9345478, 60.1575712),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.934729686260493, 60.15527176994737),
                 shapely.Point(24.9345898, 60.1552307),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.93919381647808, 60.17546866945838),
                 shapely.Point(24.9392248, 60.1754825),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.93920625448821, 60.16873712001135),
                 shapely.Point(24.9395336, 60.1688809),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.939210399174772, 60.16649326866454),
                 shapely.Point(24.9392062, 60.166541),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.939214543203153, 60.16424941655202),
                 shapely.Point(24.9395105, 60.1641026),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.939218686656393, 60.16200551878885),
                 shapely.Point(24.9394813, 60.1620553),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.93922282945177, 60.15976162025793),
                 shapely.Point(24.9391651, 60.1597873),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.93922697150651, 60.157517765848276),
                 shapely.Point(24.9392257, 60.1574796),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.93923111290369, 60.15527391067289),
                 shapely.Point(24.9393653, 60.1553187),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.94369793061261, 60.1754706588567),
                 shapely.Point(24.9440337, 60.1754045),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.943701770118057, 60.17322680962688),
                 shapely.Point(24.9437021, 60.1731434),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.943705609013687, 60.17098295963162),
                 shapely.Point(24.9433897, 60.1708795),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.943709447299632, 60.16873910887079),
                 shapely.Point(24.9437092, 60.1687734),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.94371328497602, 60.166495257344394),
                 shapely.Point(24.9437549, 60.1664853),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.943717122043, 60.16425140505232),
                 shapely.Point(24.9438497, 60.1643061),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.943720958577437, 60.162007507109614),
                 shapely.Point(24.9437158, 60.1620892),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.94372479450272, 60.159763608399196),
                 shapely.Point(24.9440714, 60.1598923),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.943728629742253, 60.15751975381008),
                 shapely.Point(24.9439189, 60.1575247),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.94373246437293, 60.15527589845523),
                 shapely.Point(24.9437321, 60.1552857),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.948205577705817, 60.17322864583047),
                 shapely.Point(24.9482066, 60.1730688),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.94820910949361, 60.17098479566939),
                 shapely.Point(24.9482103, 60.1707875),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
                 shapely.Point(24.948216171386584, 60.166497093050616),
                 shapely.Point(24.9478373, 60.1665188),
             ),
         ],
     )
-    def test_find_split(self, transport_network_, point, snapped_point):
-        street_layer = transport_network_.street_layer
+    def test_find_split(self, transport_network, point, snapped_point):
+        street_layer = transport_network.street_layer
         assert isinstance(street_layer, r5py.r5.StreetLayer)
         assert street_layer.find_split(point) == snapped_point
diff --git a/tests/test_travel_time_matrix_computer.py b/tests/test_travel_time_matrix_computer.py
index 2842c6e4..9e97e55a 100644
--- a/tests/test_travel_time_matrix_computer.py
+++ b/tests/test_travel_time_matrix_computer.py
@@ -34,16 +34,10 @@ def test_origins_invalid_data(self, transport_network, origins, expected_error):
             )
             del travel_time_matrix_computer
 
-    @pytest.mark.parametrize(
-        [
-            "origins",
-        ],
-        [(pytest.lazy_fixture("origins_valid_ids"),)],
-    )
-    def test_origins_valid_data(self, transport_network, origins):
+    def test_origins_valid_data(self, transport_network, origins_valid_ids):
         travel_time_matrix_computer = r5py.TravelTimeMatrixComputer(
             transport_network,
-            origins=origins,
+            origins=origins_valid_ids,
         )
         del travel_time_matrix_computer
 
@@ -77,7 +71,7 @@ def test_origins_valid_data(self, transport_network, origins):
         ],
     )
     def test_origins_and_destinations_invalid_data(
-        self, transport_network, origins, destinations, expected_error
+        self, transport_network, origins, destinations, expected_error,
     ):
         with pytest.raises(expected_error):
             travel_time_matrix_computer = r5py.TravelTimeMatrixComputer(
@@ -100,7 +94,7 @@ def test_origins_and_destinations_invalid_data(
         ],
     )
     def test_origins_and_destinations_valid_data(
-        self, transport_network, origins, destinations
+        self, transport_network, origins, destinations,
     ):
         travel_time_matrix_computer = r5py.TravelTimeMatrixComputer(
             transport_network,
@@ -112,7 +106,7 @@ def test_origins_and_destinations_valid_data(
 
 class TestTravelTimeMatrixComputer:
     def test_travel_time_matrix_initialization(
-        self, transport_network, population_grid_points, origin_point
+        self, transport_network, population_grid_points, origin_point,
     ):
         travel_time_matrix_computer = r5py.TravelTimeMatrixComputer(
             transport_network,
@@ -242,62 +236,60 @@ def test_gtfs_date_range_warnings_without_gtfs_file(
 
     @pytest.mark.parametrize(
         [
-            "transport_network_",
-            "origins",
             "snap_to_network",
             "expected_snap_to_network",
         ],
         [
             (
-                pytest.lazy_fixture("transport_network"),
-                pytest.lazy_fixture("population_grid_points"),
                 True,
                 True,
             ),
             (
-                pytest.lazy_fixture("transport_network"),
-                pytest.lazy_fixture("population_grid_points"),
                 False,
                 False,
             ),
         ],
     )
     def test_snap_to_network_parameter(
-        self, transport_network_, origins, snap_to_network, expected_snap_to_network
+        self,
+        transport_network,
+        population_grid_points,
+        snap_to_network,
+        expected_snap_to_network,
     ):
         travel_time_matrix_computer = r5py.TravelTimeMatrixComputer(
-            transport_network_, origins, snap_to_network=snap_to_network
+            transport_network,
+            population_grid_points,
+            snap_to_network=snap_to_network,
         )
         assert travel_time_matrix_computer.snap_to_network == expected_snap_to_network
 
     @pytest.mark.parametrize(
         [
-            "transport_network_",
-            "origins",
             "snap_to_network",
             "expected_travel_times",
         ],
         [
             (
-                pytest.lazy_fixture("transport_network"),
-                pytest.lazy_fixture("population_grid_points"),
                 True,
                 pytest.lazy_fixture("walking_times_snapped"),
             ),
             (
-                pytest.lazy_fixture("transport_network"),
-                pytest.lazy_fixture("population_grid_points"),
                 False,
                 pytest.lazy_fixture("walking_times_not_snapped"),
             ),
         ],
     )
     def test_snap_to_network(
-        self, transport_network_, origins, snap_to_network, expected_travel_times
+        self,
+        transport_network,
+        population_grid_points,
+        snap_to_network,
+        expected_travel_times,
     ):
         travel_time_matrix_computer = r5py.TravelTimeMatrixComputer(
-            transport_network_,
-            origins,
+            transport_network,
+            origins=population_grid_points,
             snap_to_network=snap_to_network,
             transport_modes=[r5py.LegMode.WALK],
         )
@@ -311,7 +303,9 @@ def test_snap_to_network(
         assert travel_times.equals(expected_travel_times)
 
     def test_snap_to_network_with_unsnappable_origins(
-        self, transport_network, unsnappable_points
+        self,
+        transport_network,
+        unsnappable_points,
     ):
         with pytest.warns(RuntimeWarning):
             _ = r5py.TravelTimeMatrixComputer(
@@ -322,7 +316,10 @@ def test_snap_to_network_with_unsnappable_origins(
             )
 
     def test_snap_to_network_with_unsnappable_destinations(
-        self, transport_network, population_grid_points, unsnappable_points
+        self,
+        transport_network,
+        population_grid_points,
+        unsnappable_points,
     ):
         with pytest.warns(RuntimeWarning):
             _ = r5py.TravelTimeMatrixComputer(
@@ -332,3 +329,24 @@ def test_snap_to_network_with_unsnappable_destinations(
                 snap_to_network=True,
                 transport_modes=[r5py.LegMode.WALK],
             )
+
+    @pytest.mark.parametrize("snap_to_network", [True, False])
+    def test_travel_time_between_identical_from_and_to_ids(
+        self,
+        transport_network,
+        population_grid_points,
+        snap_to_network,
+    ):
+        travel_time_matrix = r5py.TravelTimeMatrixComputer(
+            transport_network,
+            origins=population_grid_points,
+            transport_modes=[r5py.LegMode.WALK],
+            snap_to_network=snap_to_network,
+        ).compute_travel_times()
+
+        assert (
+            travel_time_matrix[
+                travel_time_matrix["from_id"] == travel_time_matrix["to_id"]
+            ].travel_time.max()
+            == 0
+        )