Skip to content

Commit 11b7048

Browse files
committed
Merge branch 'main' into release
2 parents bb924b1 + 967097b commit 11b7048

18 files changed

Lines changed: 3686 additions & 69 deletions

.github/workflows/docs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ jobs:
1010

1111
steps:
1212
- uses: actions/checkout@v4
13+
with:
14+
fetch-depth: 0 # Fetch all history for all tags and branches
15+
ref: ${{ github.event.release.tag_name }}
1316

1417
- name: Set up Python
1518
uses: actions/setup-python@v5
@@ -20,6 +23,7 @@ jobs:
2023
run: |
2124
python -m pip install --upgrade pip
2225
pip install -r docs/requirements.txt
26+
pip install .
2327
2428
- name: Build documentation
2529
run: |

README.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
*Graph algorithms in Cython*
1515

16-
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+
Welcome to our Python library for graph algorithms. The library includes both Dijkstra's and Bellman-Ford's algorithms, with plans to add more 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.
1717

1818
Documentation : [https://edsger.readthedocs.io/en/latest/](https://edsger.readthedocs.io/en/latest/)
1919

@@ -57,6 +57,63 @@ print("Shortest paths:", shortest_paths)
5757

5858
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.
5959

60+
## Bellman-Ford Algorithm: Handling Negative Weights
61+
62+
The Bellman-Ford algorithm can handle graphs with negative edge weights and detect negative cycles, making it suitable for more complex scenarios than Dijkstra's algorithm.
63+
64+
```python
65+
from edsger.path import BellmanFord
66+
67+
# Create a graph with negative weights
68+
edges_negative = pd.DataFrame({
69+
'tail': [0, 0, 1, 1, 2, 3],
70+
'head': [1, 2, 2, 3, 3, 4],
71+
'weight': [1, 4, -2, 5, 1, 3] # Note the negative weight
72+
})
73+
edges_negative
74+
```
75+
76+
| | tail | head | weight |
77+
|---:|-------:|-------:|---------:|
78+
| 0 | 0 | 1 | 1.0 |
79+
| 1 | 0 | 2 | 4.0 |
80+
| 2 | 1 | 2 | -2.0 |
81+
| 3 | 1 | 3 | 5.0 |
82+
| 4 | 2 | 3 | 1.0 |
83+
| 5 | 3 | 4 | 3.0 |
84+
85+
```python
86+
# Initialize and run Bellman-Ford
87+
bf = BellmanFord(edges_negative)
88+
shortest_paths = bf.run(vertex_idx=0)
89+
print("Shortest paths:", shortest_paths)
90+
```
91+
92+
Shortest paths: [ 0. 1. -1. 0. 3.]
93+
94+
The Bellman-Ford algorithm finds the optimal path even with negative weights. In this example, the shortest path from node 0 to node 2 has length -1 (going 0→1→2 with weights 1 + (-2) = -1), which is shorter than the direct path 0→2 with weight 4.
95+
96+
### Negative Cycle Detection
97+
98+
Bellman-Ford can also detect negative cycles, which indicate that no shortest path exists:
99+
100+
```python
101+
# Create a graph with a negative cycle
102+
edges_cycle = pd.DataFrame({
103+
'tail': [0, 1, 2],
104+
'head': [1, 2, 0],
105+
'weight': [1, -2, -1] # Cycle 0→1→2→0 has total weight -2
106+
})
107+
108+
bf_cycle = BellmanFord(edges_cycle)
109+
try:
110+
bf_cycle.run(vertex_idx=0)
111+
except ValueError as e:
112+
print("Error:", e)
113+
```
114+
115+
Error: Negative cycle detected in the graph
116+
60117
## Installation
61118

62119
### Standard Installation

docs/source/api.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,135 @@ paths = dijkstra.run(
114114
heap_length_ratio=0.5
115115
)
116116
```
117+
118+
### BellmanFord Class
119+
120+
The class for performing Bellman-Ford shortest path algorithm, which handles graphs with negative edge weights and detects negative cycles.
121+
122+
#### Constructor Parameters
123+
124+
- `edges`: pandas.DataFrame containing the graph edges
125+
- `tail`: Column name for edge source nodes (default: 'tail')
126+
- `head`: Column name for edge destination nodes (default: 'head')
127+
- `weight`: Column name for edge weights (default: 'weight')
128+
- `orientation`: Either 'out' (single-source) or 'in' (single-target) (default: 'out')
129+
- `check_edges`: Whether to validate edge data (default: False)
130+
- `permute`: Whether to optimize node indexing (default: False)
131+
132+
#### Methods
133+
134+
##### run
135+
136+
```python
137+
def run(self, vertex_idx, path_tracking=False, return_inf=True,
138+
return_series=False, detect_negative_cycles=True)
139+
```
140+
141+
Runs the Bellman-Ford algorithm from/to the specified vertex.
142+
143+
**Parameters:**
144+
- `vertex_idx`: Source/target vertex index
145+
- `path_tracking`: Whether to track paths for reconstruction (default: False)
146+
- `return_inf`: Return infinity for unreachable vertices (default: True)
147+
- `return_series`: Return results as pandas Series (default: False)
148+
- `detect_negative_cycles`: Whether to detect negative cycles (default: True)
149+
150+
**Returns:**
151+
- Array or Series of shortest path lengths
152+
153+
**Raises:**
154+
- `ValueError`: If a negative cycle is detected and `detect_negative_cycles=True`
155+
156+
##### get_path
157+
158+
```python
159+
def get_path(self, vertex_idx)
160+
```
161+
162+
Reconstructs the shortest path to/from a vertex (requires `path_tracking=True`).
163+
164+
**Parameters:**
165+
- `vertex_idx`: Destination/source vertex index
166+
167+
**Returns:**
168+
- Array of vertex indices forming the path
169+
170+
##### get_vertices
171+
172+
```python
173+
def get_vertices(self)
174+
```
175+
176+
Returns all vertices in the graph.
177+
178+
**Returns:**
179+
- Array of vertex indices
180+
181+
#### Examples
182+
183+
##### Basic Usage with Negative Weights
184+
185+
```python
186+
from edsger.path import BellmanFord
187+
import pandas as pd
188+
189+
# Create a graph with negative weights
190+
edges = pd.DataFrame({
191+
'tail': [0, 0, 1, 1, 2, 3],
192+
'head': [1, 2, 2, 3, 3, 4],
193+
'weight': [1, 4, -2, 5, 1, 3] # Note the negative weight
194+
})
195+
196+
# Initialize Bellman-Ford
197+
bf = BellmanFord(edges)
198+
199+
# Find shortest paths from vertex 0
200+
paths = bf.run(vertex_idx=0)
201+
print(paths) # [ 0. 1. -1. 0. 3.]
202+
```
203+
204+
##### Negative Cycle Detection
205+
206+
```python
207+
# Create a graph with a negative cycle
208+
edges_cycle = pd.DataFrame({
209+
'tail': [0, 1, 2],
210+
'head': [1, 2, 0],
211+
'weight': [1, -2, -1] # Cycle 0→1→2→0 has total weight -2
212+
})
213+
214+
bf_cycle = BellmanFord(edges_cycle)
215+
try:
216+
bf_cycle.run(vertex_idx=0)
217+
except ValueError as e:
218+
print(f"Error: {e}") # Error: Negative cycle detected in the graph
219+
```
220+
221+
##### Path Tracking with Negative Weights
222+
223+
```python
224+
# Enable path tracking
225+
paths = bf.run(vertex_idx=0, path_tracking=True)
226+
227+
# Get the actual path to vertex 2 (using negative weight path)
228+
path = bf.get_path(vertex_idx=2)
229+
print(path) # Path using the negative weight edge
230+
```
231+
232+
##### Performance: Disabling Cycle Detection
233+
234+
```python
235+
# For performance when you know there are no negative cycles
236+
paths = bf.run(vertex_idx=0, detect_negative_cycles=False)
237+
```
238+
239+
## Algorithm Comparison
240+
241+
| Feature | Dijkstra | BellmanFord |
242+
|---------|----------|-------------|
243+
| **Negative weights** | ❌ No | ✅ Yes |
244+
| **Negative cycle detection** | ❌ No | ✅ Yes |
245+
| **Time complexity** | O((V + E) log V) | O(VE) |
246+
| **Space complexity** | O(V) | O(V) |
247+
| **Use case** | Positive weights only | Any weights, cycle detection |
248+
| **Performance** | Faster | Slower but more versatile |

docs/source/index.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ github_url: https://github.com/aetperf/Edsger
66

77
*Graph algorithms in Cython*
88

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.
9+
Welcome to the Edsger documentation! Edsger is a Python library for efficient graph algorithms implemented in Cython. The library focuses on shortest path algorithms, featuring so far both Dijkstra's algorithm for positive-weight graphs and Bellman-Ford algorithm for graphs with negative weights and cycle detection.
1010

1111
## Why Use Edsger?
1212

@@ -18,7 +18,7 @@ Edsger is designed to be **dataframe-friendly**, providing seamless integration
1818

1919
```python
2020
import pandas as pd
21-
from edsger.path import Dijkstra
21+
from edsger.path import Dijkstra, BellmanFord
2222

2323
# Your graph data is already in a DataFrame
2424
edges = pd.DataFrame({
@@ -27,19 +27,21 @@ edges = pd.DataFrame({
2727
'weight': [1.0, 2.0, 1.5, 1.0]
2828
})
2929

30-
# No conversion needed - use directly!
31-
dijkstra = Dijkstra(edges, orientation="out")
30+
# Use Dijkstra for positive weights (faster)
31+
dijkstra = Dijkstra(edges)
3232
distances = dijkstra.run(vertex_idx=0)
33-
distances
33+
34+
# Use Bellman-Ford for negative weights or cycle detection
35+
bf = BellmanFord(edges)
36+
distances = bf.run(vertex_idx=0)
3437
```
35-
array([0., 1., 2., 3.])
3638

3739
## Key Features
3840

3941
- **Native pandas DataFrame support** - No graph object conversion required
40-
- **High performance** - Cython implementation with aggressive optimizations
41-
- **Memory efficient** - Optimized for large-scale real-world datasets
42-
- **Easy integration** with NumPy and pandas workflows
42+
- **High performance** - Cython implementation
43+
- **Memory efficient** - Optimized for real-world datasets
44+
- **Easy integration** with NumPy and Pandas workflows
4345
- **Production ready** - Comprehensive testing across Python 3.9-3.13
4446

4547
## Quick Links

0 commit comments

Comments
 (0)