edsger 0.1.1__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {edsger-0.1.1 → edsger-0.1.3}/.github/workflows/publish.yml +28 -43
  2. {edsger-0.1.1 → edsger-0.1.3}/.github/workflows/tests.yml +12 -4
  3. {edsger-0.1.1 → edsger-0.1.3}/PKG-INFO +51 -2
  4. edsger-0.1.3/README.md +77 -0
  5. edsger-0.1.3/docs/source/assets/dijkstra_benchmark_comparison.png +0 -0
  6. edsger-0.1.3/docs/source/index.md +82 -0
  7. {edsger-0.1.1 → edsger-0.1.3}/docs/source/quickstart.md +144 -25
  8. {edsger-0.1.1 → edsger-0.1.3}/requirements-dev.txt +1 -0
  9. edsger-0.1.3/scripts/benchmark_comparison.py +442 -0
  10. {edsger-0.1.1 → edsger-0.1.3}/scripts/dijkstra_dimacs.py +4 -4
  11. edsger-0.1.3/scripts/requirements.txt +11 -0
  12. {edsger-0.1.1 → edsger-0.1.3}/setup.py +8 -1
  13. edsger-0.1.3/src/edsger/_version.py +1 -0
  14. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/commons.c +151 -147
  15. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/dijkstra.c +7197 -2762
  16. edsger-0.1.3/src/edsger/dijkstra.pyx +1029 -0
  17. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/path.py +145 -37
  18. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/path_tracking.c +151 -147
  19. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/path_tracking.pyx +2 -2
  20. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/pq_4ary_dec_0b.c +203 -199
  21. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/pq_4ary_dec_0b.pxd +7 -7
  22. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/pq_4ary_dec_0b.pyx +14 -16
  23. edsger-0.1.3/src/edsger/prefetch_compat.h +21 -0
  24. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/spiess_florian.c +157 -176
  25. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/star.c +151 -147
  26. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/star.pyx +3 -3
  27. {edsger-0.1.1 → edsger-0.1.3}/src/edsger.egg-info/PKG-INFO +51 -2
  28. {edsger-0.1.1 → edsger-0.1.3}/src/edsger.egg-info/SOURCES.txt +4 -0
  29. {edsger-0.1.1 → edsger-0.1.3}/tests/test_path.py +181 -0
  30. edsger-0.1.1/README.md +0 -28
  31. edsger-0.1.1/docs/source/index.md +0 -54
  32. edsger-0.1.1/src/edsger/_version.py +0 -1
  33. edsger-0.1.1/src/edsger/dijkstra.pyx +0 -504
  34. {edsger-0.1.1 → edsger-0.1.3}/.github/workflows/docs.yml +0 -0
  35. {edsger-0.1.1 → edsger-0.1.3}/.gitignore +0 -0
  36. {edsger-0.1.1 → edsger-0.1.3}/.pre-commit-config.yaml +0 -0
  37. {edsger-0.1.1 → edsger-0.1.3}/.readthedocs.yaml +0 -0
  38. {edsger-0.1.1 → edsger-0.1.3}/AUTHORS.rst +0 -0
  39. {edsger-0.1.1 → edsger-0.1.3}/CHANGELOG.rst +0 -0
  40. {edsger-0.1.1 → edsger-0.1.3}/CONTRIBUTING.rst +0 -0
  41. {edsger-0.1.1 → edsger-0.1.3}/LICENSE +0 -0
  42. {edsger-0.1.1 → edsger-0.1.3}/MANIFEST.in +0 -0
  43. {edsger-0.1.1 → edsger-0.1.3}/docs/Makefile +0 -0
  44. {edsger-0.1.1 → edsger-0.1.3}/docs/requirements.txt +0 -0
  45. {edsger-0.1.1 → edsger-0.1.3}/docs/source/api.md +0 -0
  46. {edsger-0.1.1 → edsger-0.1.3}/docs/source/conf.py +0 -0
  47. {edsger-0.1.1 → edsger-0.1.3}/docs/source/contributing.md +0 -0
  48. {edsger-0.1.1 → edsger-0.1.3}/docs/source/installation.md +0 -0
  49. {edsger-0.1.1 → edsger-0.1.3}/pyproject.toml +0 -0
  50. {edsger-0.1.1 → edsger-0.1.3}/requirements.txt +0 -0
  51. {edsger-0.1.1 → edsger-0.1.3}/setup.cfg +0 -0
  52. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/.gitignore +0 -0
  53. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/__init__.py +0 -0
  54. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/commons.pxd +0 -0
  55. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/commons.pyx +0 -0
  56. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/networks.py +0 -0
  57. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/spiess_florian.pyx +0 -0
  58. {edsger-0.1.1 → edsger-0.1.3}/src/edsger/utils.py +0 -0
  59. {edsger-0.1.1 → edsger-0.1.3}/src/edsger.egg-info/dependency_links.txt +0 -0
  60. {edsger-0.1.1 → edsger-0.1.3}/src/edsger.egg-info/not-zip-safe +0 -0
  61. {edsger-0.1.1 → edsger-0.1.3}/src/edsger.egg-info/requires.txt +0 -0
  62. {edsger-0.1.1 → edsger-0.1.3}/src/edsger.egg-info/top_level.txt +0 -0
  63. {edsger-0.1.1 → edsger-0.1.3}/tests/test_dijkstra.py +0 -0
  64. {edsger-0.1.1 → edsger-0.1.3}/tests/test_path_tracking.py +0 -0
  65. {edsger-0.1.1 → edsger-0.1.3}/tests/test_pq_4ary_dec_0b.py +0 -0
  66. {edsger-0.1.1 → edsger-0.1.3}/tests/test_spiess_florian.py +0 -0
@@ -1,9 +1,10 @@
1
- name: ci-publish # Publish Python distribution to PyPI
1
+ name: ci-publish # Publish Python distribution to PyPI
2
2
 
3
3
  on:
4
4
  push:
5
5
  tags:
6
6
  - 'v[0-9]+.[0-9]+.[0-9]+'
7
+
7
8
  jobs:
8
9
 
9
10
  test:
@@ -13,47 +14,34 @@ jobs:
13
14
  python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
14
15
  runs-on: ${{ matrix.os }}
15
16
  steps:
16
- - uses: actions/checkout@v4
17
- - name: Set up Python ${{ matrix.python-version }}
18
- uses: actions/setup-python@v4
19
- with:
20
- python-version: ${{ matrix.python-version }}
21
- - name: Install dependencies
22
- run: |
23
- python -m pip install --upgrade pip
24
- pip install -r requirements-dev.txt
25
- pip install .
26
- - name: Testing
27
- run: |
28
- python -m pytest tests
17
+ - uses: actions/checkout@v4
18
+ - name: Set up Python ${{ matrix.python-version }}
19
+ uses: actions/setup-python@v4
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - name: Install dependencies
23
+ run: |
24
+ python -m pip install --upgrade pip
25
+ pip install -r requirements-dev.txt
26
+ pip install .
27
+ - name: Testing
28
+ run: python -m pytest tests
29
29
 
30
30
  build_source_dist:
31
31
  name: Build source distribution
32
32
  runs-on: ubuntu-latest
33
33
  steps:
34
34
  - uses: actions/checkout@v4
35
- # - if: github.event.ref_type != 'tag'
36
- # run: |
37
- # git fetch --prune --unshallow
38
- # git tag -d $(git tag --points-at HEAD)
39
- - if: github.event.ref_type == 'tag'
40
- uses: actions/checkout@v3
41
- - if: github.event_name == 'workflow_dispatch'
42
- uses: actions/checkout@v3
43
- with:
44
- fetch-depth: 0
45
35
  - uses: actions/setup-python@v4
46
36
  with:
47
37
  python-version: "3.11"
48
-
49
38
  - name: Install build
50
39
  run: python -m pip install build
51
-
52
40
  - name: Run build
53
41
  run: python -m build --sdist
54
-
55
42
  - uses: actions/upload-artifact@v4
56
43
  with:
44
+ name: cibw-sdist
57
45
  path: ./dist/*.tar.gz
58
46
 
59
47
  build_wheels:
@@ -65,23 +53,20 @@ jobs:
65
53
 
66
54
  steps:
67
55
  - uses: actions/checkout@v4
68
-
69
- - uses: actions/setup-python@v3
70
-
56
+ - uses: actions/setup-python@v4
71
57
  - name: Install cibuildwheel
72
58
  run: python -m pip install cibuildwheel==2.16.5
73
-
74
59
  - name: Build wheels
60
+ env:
61
+ CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*"
75
62
  run: python -m cibuildwheel --output-dir wheelhouse
76
-
77
63
  - uses: actions/upload-artifact@v4
78
64
  with:
79
65
  name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
80
66
  path: ./wheelhouse/*.whl
81
67
 
82
68
  publish-to-pypi:
83
- name: >-
84
- Publish Python distribution to PyPI
69
+ name: Publish Python distribution to PyPI
85
70
  needs:
86
71
  - test
87
72
  - build_source_dist
@@ -89,13 +74,13 @@ jobs:
89
74
  runs-on: ubuntu-latest
90
75
  environment: release
91
76
  permissions:
92
- id-token: write # IMPORTANT: mandatory for trusted publishing
93
-
77
+ id-token: write # mandatory for trusted publishing
94
78
  steps:
95
- - name: Download all the dists
96
- uses: actions/download-artifact@v4
97
- with:
98
- name: artifact
99
- path: ./dist/
100
- - name: Publish distribution to PyPI
101
- uses: pypa/gh-action-pypi-publish@release/v1
79
+ - name: Download all the dists
80
+ uses: actions/download-artifact@v4
81
+ with:
82
+ pattern: cibw-*
83
+ path: ./dist/
84
+ merge-multiple: true
85
+ - name: Publish distribution to PyPI
86
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,4 +1,4 @@
1
- name: ci-test
1
+ name: Run tests and upload coverage
2
2
 
3
3
  on:
4
4
  push:
@@ -7,13 +7,17 @@ on:
7
7
 
8
8
  jobs:
9
9
  test:
10
+ name: Run tests and collect coverage
10
11
  strategy:
11
12
  matrix:
12
13
  os: [ubuntu-22.04, windows-2022, macos-14]
13
14
  python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
14
15
  runs-on: ${{ matrix.os }}
15
16
  steps:
16
- - uses: actions/checkout@v4
17
+ - name: Checkout
18
+ uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 2
17
21
  - name: Set up Python ${{ matrix.python-version }}
18
22
  uses: actions/setup-python@v5
19
23
  with:
@@ -29,6 +33,10 @@ jobs:
29
33
  - name: Lint Cython code
30
34
  run: |
31
35
  cython-lint src/edsger/commons.pyx src/edsger/dijkstra.pyx src/edsger/path_tracking.pyx src/edsger/pq_4ary_dec_0b.pyx src/edsger/spiess_florian.pyx src/edsger/star.pyx src/edsger/commons.pxd src/edsger/pq_4ary_dec_0b.pxd
32
- - name: Testing
36
+ - name: Run tests
33
37
  run: |
34
- python -m pytest tests
38
+ pytest --cov=src/edsger --cov-branch --cov-report=xml tests/
39
+ - name: Upload results to Codecov
40
+ uses: codecov/codecov-action@v5
41
+ with:
42
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edsger
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Graph algorithms in Cython.
5
5
  Author-email: François Pacull <francois.pacull@architecture-performance.fr>
6
6
  Maintainer-email: François Pacull <francois.pacull@architecture-performance.fr>
@@ -41,7 +41,8 @@ Dynamic: license-file
41
41
 
42
42
 
43
43
  ![Tests Status](https://github.com/aetperf/edsger/actions/workflows/tests.yml/badge.svg?branch=release)
44
- [![PyPI version](https://img.shields.io/pypi/v/edsger.svg)](https://pypi.org/project/edsger/)
44
+ [![codecov](https://codecov.io/gh/aetperf/edsger/branch/release/graph/badge.svg)](https://codecov.io/gh/aetperf/edsger)
45
+ [![PyPI version](https://img.shields.io/pypi/v/edsger.svg?refresh=1)](https://pypi.org/project/edsger/)
45
46
  [![Downloads](https://static.pepy.tech/badge/edsger)](https://pepy.tech/project/edsger)
46
47
  [![Python 3.9 | 3.10 | 3.11 | 3.12 | 3.13](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://pypi.org/project/edsger/)
47
48
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@@ -56,6 +57,54 @@ Welcome to our Python library for graph algorithms. So far, the library only inc
56
57
 
57
58
  Documentation : [https://edsger.readthedocs.io/en/latest/](https://edsger.readthedocs.io/en/latest/)
58
59
 
60
+ ## Small example : Dijkstra's Algorithm
61
+
62
+ To use Dijkstra's algorithm, you can import the `Dijkstra` class from the `path` module. The function takes a graph and a source node as input, and returns the shortest path from the source node to all other nodes in the graph.
63
+
64
+ ```python
65
+ import pandas as pd
66
+
67
+ from edsger.path import Dijkstra
68
+
69
+ # Create a DataFrame with the edges of the graph
70
+ edges = pd.DataFrame({
71
+ 'tail': [0, 0, 1, 2, 2, 3],
72
+ 'head': [1, 2, 2, 3, 4, 4],
73
+ 'weight': [1, 4, 2, 1.5, 3, 1]
74
+ })
75
+ edges
76
+ ```
77
+
78
+ | | tail | head | weight |
79
+ |---:|-------:|-------:|---------:|
80
+ | 0 | 0 | 1 | 1.0 |
81
+ | 1 | 0 | 2 | 4.0 |
82
+ | 2 | 1 | 2 | 2.0 |
83
+ | 3 | 2 | 3 | 1.5 |
84
+ | 4 | 2 | 4 | 3.0 |
85
+ | 5 | 3 | 4 | 1.0 |
86
+
87
+ ```python
88
+ # Initialize the Dijkstra object
89
+ dijkstra = Dijkstra(edges)
90
+
91
+ # Run the algorithm from a source vertex
92
+ shortest_paths = dijkstra.run(vertex_idx=0)
93
+ print("Shortest paths:", shortest_paths)
94
+ ```
95
+
96
+ Shortest paths: [0. 1. 3. 4.5 5.5]
97
+
98
+ We get the shortest paths from the source node 0 to all other nodes in the graph. The output is an array with the shortest path length to each node. A path length is the sum of the weights of the edges in the path.
99
+
100
+ ## Why Use Edsger?
101
+
102
+ Edsger is designed to be **dataframe-friendly**, providing seamless integration with pandas workflows for graph algorithms. Also it is rather efficient. Our benchmarks on the USA road network (23.9M vertices, 57.7M edges) demonstrate nice performance:
103
+
104
+ <img src="https://raw.githubusercontent.com/aetperf/edsger/release/docs/source/assets/dijkstra_benchmark_comparison.png" alt="Dijkstra Performance Comparison" width="700">
105
+
106
+ *Benchmark performed on Intel i9-12900H Linux laptop.*
107
+
59
108
  ## Contributing
60
109
 
61
110
  We welcome contributions to the Edsger library. If you have any suggestions, bug reports, or feature requests, please open an issue on our [GitHub repository](https://github.com/aetperf/Edsger).
edsger-0.1.3/README.md ADDED
@@ -0,0 +1,77 @@
1
+
2
+ ![Tests Status](https://github.com/aetperf/edsger/actions/workflows/tests.yml/badge.svg?branch=release)
3
+ [![codecov](https://codecov.io/gh/aetperf/edsger/branch/release/graph/badge.svg)](https://codecov.io/gh/aetperf/edsger)
4
+ [![PyPI version](https://img.shields.io/pypi/v/edsger.svg?refresh=1)](https://pypi.org/project/edsger/)
5
+ [![Downloads](https://static.pepy.tech/badge/edsger)](https://pepy.tech/project/edsger)
6
+ [![Python 3.9 | 3.10 | 3.11 | 3.12 | 3.13](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://pypi.org/project/edsger/)
7
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
8
+ [![Cython lint: cython-lint](https://img.shields.io/badge/cython--lint-enabled-brightgreen.svg)](https://github.com/MarcoGorelli/cython-lint)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+
11
+ # Edsger
12
+
13
+ *Graph algorithms in Cython*
14
+
15
+ Welcome to our Python library for graph algorithms. So far, the library only includes Dijkstra's algorithm but we should add a range of common path algorithms later. It is also open-source and easy to integrate with other Python libraries. To get started, simply install the library using pip, and import it into your Python project.
16
+
17
+ Documentation : [https://edsger.readthedocs.io/en/latest/](https://edsger.readthedocs.io/en/latest/)
18
+
19
+ ## Small example : Dijkstra's Algorithm
20
+
21
+ To use Dijkstra's algorithm, you can import the `Dijkstra` class from the `path` module. The function takes a graph and a source node as input, and returns the shortest path from the source node to all other nodes in the graph.
22
+
23
+ ```python
24
+ import pandas as pd
25
+
26
+ from edsger.path import Dijkstra
27
+
28
+ # Create a DataFrame with the edges of the graph
29
+ edges = pd.DataFrame({
30
+ 'tail': [0, 0, 1, 2, 2, 3],
31
+ 'head': [1, 2, 2, 3, 4, 4],
32
+ 'weight': [1, 4, 2, 1.5, 3, 1]
33
+ })
34
+ edges
35
+ ```
36
+
37
+ | | tail | head | weight |
38
+ |---:|-------:|-------:|---------:|
39
+ | 0 | 0 | 1 | 1.0 |
40
+ | 1 | 0 | 2 | 4.0 |
41
+ | 2 | 1 | 2 | 2.0 |
42
+ | 3 | 2 | 3 | 1.5 |
43
+ | 4 | 2 | 4 | 3.0 |
44
+ | 5 | 3 | 4 | 1.0 |
45
+
46
+ ```python
47
+ # Initialize the Dijkstra object
48
+ dijkstra = Dijkstra(edges)
49
+
50
+ # Run the algorithm from a source vertex
51
+ shortest_paths = dijkstra.run(vertex_idx=0)
52
+ print("Shortest paths:", shortest_paths)
53
+ ```
54
+
55
+ Shortest paths: [0. 1. 3. 4.5 5.5]
56
+
57
+ We get the shortest paths from the source node 0 to all other nodes in the graph. The output is an array with the shortest path length to each node. A path length is the sum of the weights of the edges in the path.
58
+
59
+ ## Why Use Edsger?
60
+
61
+ Edsger is designed to be **dataframe-friendly**, providing seamless integration with pandas workflows for graph algorithms. Also it is rather efficient. Our benchmarks on the USA road network (23.9M vertices, 57.7M edges) demonstrate nice performance:
62
+
63
+ <img src="https://raw.githubusercontent.com/aetperf/edsger/release/docs/source/assets/dijkstra_benchmark_comparison.png" alt="Dijkstra Performance Comparison" width="700">
64
+
65
+ *Benchmark performed on Intel i9-12900H Linux laptop.*
66
+
67
+ ## Contributing
68
+
69
+ We welcome contributions to the Edsger library. If you have any suggestions, bug reports, or feature requests, please open an issue on our [GitHub repository](https://github.com/aetperf/Edsger).
70
+
71
+ ## License
72
+
73
+ Edsger is licensed under the MIT License. See the LICENSE file for more details.
74
+
75
+ ## Contact
76
+
77
+ For any questions or inquiries, please contact François Pacull at [francois.pacull@architecture-performance.fr](mailto:francois.pacull@architecture-performance.fr).
@@ -0,0 +1,82 @@
1
+ ---
2
+ github_url: https://github.com/aetperf/Edsger
3
+ ---
4
+
5
+ # Edsger
6
+
7
+ *Graph algorithms in Cython*
8
+
9
+ Welcome to the Edsger documentation! Edsger is a Python library for efficient graph algorithms implemented in Cython. The library currently focuses on shortest path algorithms, with Dijkstra's algorithm fully implemented and additional algorithms planned for future releases.
10
+
11
+ ## Why Use Edsger?
12
+
13
+ Edsger is designed to be **dataframe-friendly**, providing seamless integration with pandas workflows for graph algorithms. Also it is rather efficient. Our benchmarks on the USA road network (23.9M vertices, 57.7M edges) demonstrate nice performance:
14
+
15
+ <img src="assets/dijkstra_benchmark_comparison.png" alt="Dijkstra Performance Comparison" width="700">
16
+
17
+ *Benchmark performed on Intel i9-12900H Linux laptop.*
18
+
19
+ ### Pandas Integration Made Simple
20
+
21
+ ```python
22
+ import pandas as pd
23
+ from edsger.path import Dijkstra
24
+
25
+ # Your graph data is already in a DataFrame
26
+ edges = pd.DataFrame({
27
+ 'tail': [0, 0, 1, 2],
28
+ 'head': [1, 2, 2, 3],
29
+ 'weight': [1.0, 2.0, 1.5, 1.0]
30
+ })
31
+
32
+ # No conversion needed - use directly!
33
+ dijkstra = Dijkstra(edges, orientation="out")
34
+ distances = dijkstra.run(vertex_idx=0)
35
+ distances
36
+ ```
37
+ array([0., 1., 2., 3.])
38
+
39
+ ## Key Features
40
+
41
+ - **Native pandas DataFrame support** - No graph object conversion required
42
+ - **High performance** - Cython implementation with aggressive optimizations
43
+ - **Memory efficient** - Optimized for large-scale real-world datasets
44
+ - **Easy integration** with NumPy and pandas workflows
45
+ - **Production ready** - Comprehensive testing across Python 3.9-3.13
46
+
47
+ ## Quick Links
48
+
49
+ - [Installation](installation.md) - How to install Edsger
50
+ - [Quick Start](quickstart.md) - Get started quickly with basic examples
51
+ - [API Reference](api.md) - Complete API reference
52
+
53
+ ## Table of Contents
54
+
55
+ ```{toctree}
56
+ :maxdepth: 2
57
+ :caption: User Guide
58
+
59
+ installation
60
+ quickstart
61
+ ```
62
+
63
+ ```{toctree}
64
+ :maxdepth: 2
65
+ :caption: API Reference
66
+
67
+ api
68
+ ```
69
+
70
+ ```{toctree}
71
+ :maxdepth: 1
72
+ :caption: Development
73
+
74
+ contributing
75
+ ```
76
+
77
+ ## Indices
78
+
79
+ - {ref}`genindex`
80
+ - {ref}`modindex`
81
+ - {ref}`search`
82
+
@@ -15,39 +15,50 @@ Edsger expects graph data as a pandas DataFrame with the following structure:
15
15
 
16
16
  Example:
17
17
  ```python
18
+ import pandas as pd
19
+
18
20
  edges = pd.DataFrame({
19
21
  'tail': [0, 0, 1, 2],
20
22
  'head': [1, 2, 2, 3],
21
23
  'weight': [1.0, 4.0, 2.0, 1.0]
22
24
  })
25
+ edges
23
26
  ```
24
27
 
28
+ | | tail | head | weight |
29
+ |---:|-------:|-------:|---------:|
30
+ | 0 | 0 | 1 | 1 |
31
+ | 1 | 0 | 2 | 4 |
32
+ | 2 | 1 | 2 | 2 |
33
+ | 3 | 2 | 3 | 1 |
34
+
35
+
36
+ Note that it is also possible to use a graph with different column names for the tail, head and weight values, but we need then to specify the name mapping, as described in the following.
37
+
25
38
  ## Dijkstra's Algorithm
26
39
 
27
40
  To use Dijkstra's algorithm, you can import the `Dijkstra` class from the `path` module. The function takes a graph and a source node as input, and returns the shortest path from the source node to all other nodes in the graph.
28
41
 
29
42
  ```python
30
- import pandas as pd
31
-
32
43
  from edsger.path import Dijkstra
33
44
 
34
45
  # Create a DataFrame with the edges of the graph
35
46
  edges = pd.DataFrame({
36
47
  'tail': [0, 0, 1, 2, 2, 3],
37
48
  'head': [1, 2, 2, 3, 4, 4],
38
- 'weight': [1, 4, 2, 1, 3, 1]
49
+ 'weight': [1, 4, 2, 1.5, 3, 1]
39
50
  })
40
51
  edges
41
52
  ```
42
53
 
43
54
  | | tail | head | weight |
44
55
  |---:|-------:|-------:|---------:|
45
- | 0 | 0 | 1 | 1 |
46
- | 1 | 0 | 2 | 4 |
47
- | 2 | 1 | 2 | 2 |
48
- | 3 | 2 | 3 | 1 |
49
- | 4 | 2 | 4 | 3 |
50
- | 5 | 3 | 4 | 1 |
56
+ | 0 | 0 | 1 | 1.0 |
57
+ | 1 | 0 | 2 | 4.0 |
58
+ | 2 | 1 | 2 | 2.0 |
59
+ | 3 | 2 | 3 | 1.5 |
60
+ | 4 | 2 | 4 | 3.0 |
61
+ | 5 | 3 | 4 | 1.0 |
51
62
 
52
63
 
53
64
  ```python
@@ -59,19 +70,19 @@ shortest_paths = dijkstra.run(vertex_idx=0)
59
70
  print("Shortest paths:", shortest_paths)
60
71
  ```
61
72
 
62
- Shortest paths: [0. 1. 3. 4. 5.]
73
+ Shortest paths: [0. 1. 3. 4.5 5.5]
63
74
 
64
75
  We get the shortest paths from the source node 0 to all other nodes in the graph. The output is an array with the shortest path length to each node. A path length is the sum of the weights of the edges in the path.
65
76
 
66
- It is also possible to use a graph with different column names for the tail, head and weight values. The column names can be specified using the `tail`, `head` and `weight` arguments:
77
+ The column names can be specified using the `tail`, `head` and `weight` arguments:
67
78
 
68
79
  ```python
69
80
  other_edges = pd.DataFrame({
70
81
  'from': [0, 0, 1, 2, 2, 3],
71
82
  'to': [1, 2, 2, 3, 4, 4],
72
- 'travel_time': [1, 4, 2, 1, 3, 1]
83
+ 'travel_time': [1, 4, 2, 1.5, 3, 1]
73
84
  })
74
- other_dijkstra = Dijkstra(edges, tail='from', head='to', weight='travel_time')
85
+ other_dijkstra = Dijkstra(other_edges, tail='from', head='to', weight='travel_time')
75
86
  ```
76
87
 
77
88
  ### Orientation
@@ -98,7 +109,7 @@ shortest_paths = dijkstra.run(vertex_idx=4)
98
109
  print("Shortest paths:", shortest_paths)
99
110
  ```
100
111
 
101
- Shortest paths: [5. 4. 2. 1. 0.]
112
+ Shortest paths: [5.5 4.5 2.5 1. 0. ]
102
113
 
103
114
  ### Check Edges
104
115
 
@@ -131,7 +142,7 @@ SHIFT = 1000
131
142
  shifted_edges = pd.DataFrame({
132
143
  'tail': [0, 0, 1, 2, 2, 3],
133
144
  'head': [1, 2, 2, 3, 4, 4],
134
- 'weight': [1, 4, 2, 1, 3, 1]
145
+ 'weight': [1, 4, 2, 1.5, 3, 1]
135
146
  })
136
147
  shifted_edges["tail"] += SHIFT
137
148
  shifted_edges["head"] += SHIFT
@@ -140,9 +151,9 @@ shifted_edges.head(3)
140
151
 
141
152
  | | tail | head | weight |
142
153
  |---:|-------:|-------:|---------:|
143
- | 0 | 1000 | 1001 | 1 |
144
- | 1 | 1000 | 1002 | 4 |
145
- | 2 | 1001 | 1002 | 2 |
154
+ | 0 | 1000 | 1001 | 1.0 |
155
+ | 1 | 1000 | 1002 | 4.0 |
156
+ | 2 | 1001 | 1002 | 2.0 |
146
157
 
147
158
 
148
159
 
@@ -152,18 +163,124 @@ shortest_paths = dijkstra.run(vertex_idx=0 + SHIFT)
152
163
  print("Shortest paths:", shortest_paths)
153
164
  ```
154
165
 
155
- Shortest paths: [inf inf inf ... 3. 4. 5.]
166
+ Shortest paths: [inf inf inf ... 3. 4.5 5.5]
156
167
 
157
168
  ```python
158
169
  shortest_paths[-5:]
159
170
  ```
160
171
 
161
- array([0., 1., 3., 4., 5.])
172
+ array([0. , 1. , 3. , 4.5, 5.5])
173
+
174
+ ### Early Termination
175
+
176
+ Early termination is a performance optimization feature that allows Dijkstra's algorithm to stop computing once specific target nodes (termination nodes) have been reached. This can significantly reduce computation time when you only need shortest paths to a subset of vertices in the graph.
177
+
178
+ When using early termination, the algorithm will:
179
+ 1. Stop as soon as all specified termination nodes have been visited
180
+ 2. Return **only** the path lengths to the termination nodes (not all vertices)
181
+ 3. Return results in the same order as the termination nodes were specified
182
+
183
+ #### Basic Early Termination Example
184
+
185
+ ```python
186
+ import pandas as pd
187
+ from edsger.path import Dijkstra
188
+
189
+ # Create a sample graph
190
+ edges = pd.DataFrame({
191
+ "tail": [0, 0, 0, 1, 1, 2, 2, 3, 3, 4],
192
+ "head": [1, 2, 3, 2, 4, 3, 5, 4, 5, 5],
193
+ "weight": [1.0, 4.0, 2.0, 1.0, 3.0, 1.0, 2.0, 1.0, 1.0, 1.0],
194
+ })
195
+ ```
196
+
197
+ **Without early termination** (computes paths to all vertices):
198
+ ```python
199
+ dijkstra = Dijkstra(edges, orientation="out")
200
+ distances = dijkstra.run(vertex_idx=0)
201
+ print("All distances:", distances)
202
+ ```
203
+ All distances: [0. 1. 2. 2. 3. 3.]
204
+
205
+ **With early termination** (computes paths only to specified nodes):
206
+ ```python
207
+ # Only compute paths to nodes 3 and 5
208
+ termination_nodes = [3, 5]
209
+ distances = dijkstra.run(vertex_idx=0, termination_nodes=termination_nodes)
210
+ print("Distances to termination nodes:", distances)
211
+ print("Shape of result:", distances.shape)
212
+ ```
213
+ Distances to termination nodes: [2. 3.]
214
+ Shape of result: (2,)
215
+
216
+ Notice that:
217
+ - The result array has length 2 (same as number of termination nodes)
218
+ - `distances[0] = 2.0` is the shortest path length from vertex 0 to vertex 3
219
+ - `distances[1] = 3.0` is the shortest path length from vertex 0 to vertex 5
220
+
221
+ #### Early Termination with Path Tracking
222
+
223
+ Early termination also works with path tracking enabled:
224
+
225
+ ```python
226
+ dijkstra = Dijkstra(edges, orientation="out")
227
+ distances = dijkstra.run(vertex_idx=0, termination_nodes=[3, 5], path_tracking=True)
228
+ print("Distances:", distances)
229
+
230
+ # Get paths to termination nodes
231
+ path_to_3 = dijkstra.get_path(vertex_idx=3)
232
+ path_to_5 = dijkstra.get_path(vertex_idx=5)
233
+ print("Path to vertex 3:", path_to_3)
234
+ print("Path to vertex 5:", path_to_5)
235
+ ```
236
+ Distances: [2. 3.]
237
+ Path to vertex 3: [3 2 1 0]
238
+ Path to vertex 5: [5 3 2 1 0]
239
+
240
+ #### Important Notes
241
+
242
+ 1. **Return Array Size**: With early termination, the returned array size equals the number of termination nodes, not the total number of vertices in the graph.
243
+
244
+ 2. **Order Preservation**: Results are returned in the same order as the termination nodes are specified:
245
+ ```python
246
+ # Termination nodes [3, 5] → results [distance_to_3, distance_to_5]
247
+ # Termination nodes [5, 3] → results [distance_to_5, distance_to_3]
248
+ ```
249
+
250
+ 3. **Orientation Support**: Early termination works with both orientations:
251
+ ```python
252
+ # Single-source shortest paths (from source to termination nodes)
253
+ dijkstra = Dijkstra(edges, orientation="out")
254
+ distances = dijkstra.run(vertex_idx=0, termination_nodes=[3, 5])
255
+
256
+ # Single-target shortest paths (from termination nodes to target)
257
+ dijkstra = Dijkstra(edges, orientation="in")
258
+ distances = dijkstra.run(vertex_idx=5, termination_nodes=[0, 2])
259
+ ```
260
+
261
+ 4. **Unreachable Nodes**: If a termination node is unreachable, its distance will be infinity:
262
+ ```python
263
+ # If node 10 is unreachable from node 0
264
+ distances = dijkstra.run(vertex_idx=0, termination_nodes=[3, 10])
265
+ # Result: [2.0, inf]
266
+ ```
162
267
 
163
268
  ### Run Method Options
164
269
 
165
270
  The `run` method can take the following arguments besides the source/target vertex index:
166
271
 
272
+ - `termination_nodes` : list or array-like, optional (default=None)
273
+
274
+ A list or array of vertex indices where the algorithm should stop early. When specified, the algorithm will terminate as soon as all termination nodes have been reached, and will return only the path lengths to these nodes in the same order they were specified. This can provide significant performance improvements when you only need paths to a subset of vertices.
275
+
276
+ ```python
277
+ dijkstra = Dijkstra(edges)
278
+ # Get distances only to nodes 2 and 4
279
+ distances = dijkstra.run(vertex_idx=0, termination_nodes=[2, 4])
280
+ print("Distances to nodes 2 and 4:", distances)
281
+ ```
282
+ Distances to nodes 2 and 4: [1.5 3.5]
283
+
167
284
  - `path_tracking` : bool, optional (default=False)
168
285
 
169
286
  Whether to track the shortest path(s) from/to the source/target vertex to all other vertices in the graph.
@@ -184,6 +301,8 @@ dijkstra.get_path(vertex_idx=0)
184
301
 
185
302
  The path is returned as an array of vertex indices. This is an ordered list of vertices from the source to the target vertex if `orientation` is `'in'`, and from the target to the source vertex if `orientation` is `'out'`. Both the source and target vertices are included in the path.
186
303
 
304
+ **Note**: When using `termination_nodes` with `path_tracking=True`, you can still retrieve paths to any vertex that was reached during the computation using `get_path()`, even if it wasn't in the termination nodes list.
305
+
187
306
  - `return_inf` : bool, optional (default=True)
188
307
 
189
308
  Whether to return path lengths as infinity (np.inf) when no path exists.
@@ -210,11 +329,11 @@ shortest_paths
210
329
 
211
330
  | vertex_idx | path_length |
212
331
  |-------------:|--------------:|
213
- | 0 | 5 |
214
- | 1 | 4 |
215
- | 2 | 2 |
216
- | 3 | 1 |
217
- | 4 | 0 |
332
+ | 0 | 5.5 |
333
+ | 1 | 4.5 |
334
+ | 2 | 2.5 |
335
+ | 3 | 1.0 |
336
+ | 4 | 0.0 |
218
337
 
219
338
 
220
339
  - `heap_length_ratio` : float, optional (default=1.0)
@@ -4,6 +4,7 @@
4
4
  # Testing
5
5
  scipy
6
6
  pytest
7
+ pytest-cov
7
8
 
8
9
  # Scripts
9
10
  loguru