Skip to content

Commit 0ec113b

Browse files
authored
Loosen trait constraints and simplify structure for longest_path (#1195)
In the recently merged #1192 a new generic DAG longest_path function was added to rustworkx-core. However, the trait bounds on the function were a bit tighter than they needed to be. The traits were forcing NodeId to be of a NodeIndex type and this wasn't really required. The only requirement that the NodeId type can be put on a hashmap and do a partial compare (that implements Hash, Eq, and PartialOrd). Also the IntoNeighborsDirected wasn't required because it's methods weren't ever used. This commit loosens the traits bounds to facilitate this. At the same time this also simplifies the code structure a bit to reduce the separation of the rust code structure in the rustworkx crate using longest_path().
1 parent 8e81911 commit 0ec113b

File tree

3 files changed

+62
-84
lines changed

3 files changed

+62
-84
lines changed

rustworkx-core/src/dag_algo.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1010
// License for the specific language governing permissions and limitations
1111
// under the License.
12+
13+
use std::cmp::Eq;
14+
use std::hash::Hash;
15+
1216
use hashbrown::HashMap;
1317

1418
use petgraph::algo;
15-
use petgraph::graph::NodeIndex;
1619
use petgraph::visit::{
17-
EdgeRef, GraphBase, GraphProp, IntoEdgesDirected, IntoNeighborsDirected, IntoNodeIdentifiers,
18-
Visitable,
20+
EdgeRef, GraphBase, GraphProp, IntoEdgesDirected, IntoNodeIdentifiers, Visitable,
1921
};
2022
use petgraph::Directed;
2123

@@ -51,7 +53,6 @@ type LongestPathResult<G, T, E> = Result<Option<(Vec<NodeId<G>>, T)>, E>;
5153
/// # Example
5254
/// ```
5355
/// use petgraph::graph::DiGraph;
54-
/// use petgraph::graph::NodeIndex;
5556
/// use petgraph::Directed;
5657
/// use rustworkx_core::dag_algo::longest_path;
5758
///
@@ -69,14 +70,10 @@ type LongestPathResult<G, T, E> = Result<Option<(Vec<NodeId<G>>, T)>, E>;
6970
/// ```
7071
pub fn longest_path<G, F, T, E>(graph: G, mut weight_fn: F) -> LongestPathResult<G, T, E>
7172
where
72-
G: GraphProp<EdgeType = Directed>
73-
+ IntoNodeIdentifiers
74-
+ IntoNeighborsDirected
75-
+ IntoEdgesDirected
76-
+ Visitable
77-
+ GraphBase<NodeId = NodeIndex>,
73+
G: GraphProp<EdgeType = Directed> + IntoNodeIdentifiers + IntoEdgesDirected + Visitable,
7874
F: FnMut(G::EdgeRef) -> Result<T, E>,
7975
T: Num + Zero + PartialOrd + Copy,
76+
<G as GraphBase>::NodeId: Hash + Eq + PartialOrd,
8077
{
8178
let mut path: Vec<NodeId<G>> = Vec::new();
8279
let nodes = match algo::toposort(graph, None) {
@@ -88,20 +85,20 @@ where
8885
return Ok(Some((path, T::zero())));
8986
}
9087

91-
let mut dist: HashMap<NodeIndex, (T, NodeIndex)> = HashMap::with_capacity(nodes.len()); // Stores the distance and the previous node
88+
let mut dist: HashMap<G::NodeId, (T, G::NodeId)> = HashMap::with_capacity(nodes.len()); // Stores the distance and the previous node
9289

9390
// Iterate over nodes in topological order
9491
for node in nodes {
9592
let parents = graph.edges_directed(node, petgraph::Direction::Incoming);
96-
let mut incoming_path: Vec<(T, NodeIndex)> = Vec::new(); // Stores the distance and the previous node for each parent
93+
let mut incoming_path: Vec<(T, G::NodeId)> = Vec::new(); // Stores the distance and the previous node for each parent
9794
for p_edge in parents {
9895
let p_node = p_edge.source();
9996
let weight: T = weight_fn(p_edge)?;
10097
let length = dist[&p_node].0 + weight;
10198
incoming_path.push((length, p_node));
10299
}
103100
// Determine the maximum distance and corresponding parent node
104-
let max_path: (T, NodeIndex) = incoming_path
101+
let max_path: (T, G::NodeId) = incoming_path
105102
.into_iter()
106103
.max_by(|a, b| a.0.partial_cmp(&b.0).unwrap())
107104
.unwrap_or((T::zero(), node)); // If there are no incoming edges, the distance is zero
@@ -114,7 +111,7 @@ where
114111
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
115112
.unwrap();
116113
let mut v = *first;
117-
let mut u: Option<NodeIndex> = None;
114+
let mut u: Option<G::NodeId> = None;
118115
// Backtrack from this node to find the path
119116
while u.map_or(true, |u| u != v) {
120117
path.push(v);

src/dag_algo/longest_path.rs

Lines changed: 0 additions & 64 deletions
This file was deleted.

src/dag_algo/mod.rs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
// License for the specific language governing permissions and limitations
1111
// under the License.
1212

13-
mod longest_path;
14-
1513
use super::DictMap;
1614
use hashbrown::{HashMap, HashSet};
1715
use indexmap::IndexSet;
@@ -22,6 +20,7 @@ use std::collections::BinaryHeap;
2220
use super::iterators::NodeIndices;
2321
use crate::{digraph, DAGHasCycle, InvalidNode, StablePyGraph};
2422

23+
use rustworkx_core::dag_algo::longest_path as core_longest_path;
2524
use rustworkx_core::traversal::dfs_edges;
2625

2726
use pyo3::exceptions::PyValueError;
@@ -32,8 +31,54 @@ use pyo3::Python;
3231
use petgraph::algo;
3332
use petgraph::graph::NodeIndex;
3433
use petgraph::prelude::*;
34+
use petgraph::stable_graph::EdgeReference;
3535
use petgraph::visit::NodeCount;
3636

37+
use num_traits::{Num, Zero};
38+
39+
/// Calculate the longest path in a directed acyclic graph (DAG).
40+
///
41+
/// This function interfaces with the Python `PyDiGraph` object to compute the longest path
42+
/// using the provided weight function.
43+
///
44+
/// # Arguments
45+
/// * `graph`: Reference to a `PyDiGraph` object.
46+
/// * `weight_fn`: A callable that takes the source node index, target node index, and the weight
47+
/// object and returns the weight of the edge as a `PyResult<T>`.
48+
///
49+
/// # Type Parameters
50+
/// * `F`: Type of the weight function.
51+
/// * `T`: The type of the edge weight. Must implement `Num`, `Zero`, `PartialOrd`, and `Copy`.
52+
///
53+
/// # Returns
54+
/// * `PyResult<(Vec<G::NodeId>, T)>` representing the longest path as a sequence of node indices and its total weight.
55+
fn longest_path<F, T>(graph: &digraph::PyDiGraph, mut weight_fn: F) -> PyResult<(Vec<usize>, T)>
56+
where
57+
F: FnMut(usize, usize, &PyObject) -> PyResult<T>,
58+
T: Num + Zero + PartialOrd + Copy,
59+
{
60+
let dag = &graph.graph;
61+
62+
// Create a new weight function that matches the required signature
63+
let edge_cost = |edge_ref: EdgeReference<'_, PyObject>| -> Result<T, PyErr> {
64+
let source = edge_ref.source().index();
65+
let target = edge_ref.target().index();
66+
let weight = edge_ref.weight();
67+
weight_fn(source, target, weight)
68+
};
69+
70+
let (path, path_weight) = match core_longest_path(dag, edge_cost) {
71+
Ok(Some((path, path_weight))) => (
72+
path.into_iter().map(NodeIndex::index).collect(),
73+
path_weight,
74+
),
75+
Ok(None) => return Err(DAGHasCycle::new_err("The graph contains a cycle")),
76+
Err(e) => return Err(e),
77+
};
78+
79+
Ok((path, path_weight))
80+
}
81+
3782
/// Return a pair of [`petgraph::Direction`] values corresponding to the "forwards" and "backwards"
3883
/// direction of graph traversal, based on whether the graph is being traved forwards (following
3984
/// the edges) or backward (reversing along edges). The order of returns is (forwards, backwards).
@@ -82,7 +127,7 @@ pub fn dag_longest_path(
82127
}
83128
};
84129
Ok(NodeIndices {
85-
nodes: longest_path::longest_path(graph, edge_weight_callable)?.0,
130+
nodes: longest_path(graph, edge_weight_callable)?.0,
86131
})
87132
}
88133

@@ -121,7 +166,7 @@ pub fn dag_longest_path_length(
121166
None => Ok(1),
122167
}
123168
};
124-
let (_, path_weight) = longest_path::longest_path(graph, edge_weight_callable)?;
169+
let (_, path_weight) = longest_path(graph, edge_weight_callable)?;
125170
Ok(path_weight)
126171
}
127172

@@ -163,7 +208,7 @@ pub fn dag_weighted_longest_path(
163208
Ok(float_res)
164209
};
165210
Ok(NodeIndices {
166-
nodes: longest_path::longest_path(graph, edge_weight_callable)?.0,
211+
nodes: longest_path(graph, edge_weight_callable)?.0,
167212
})
168213
}
169214

@@ -204,7 +249,7 @@ pub fn dag_weighted_longest_path_length(
204249
}
205250
Ok(float_res)
206251
};
207-
let (_, path_weight) = longest_path::longest_path(graph, edge_weight_callable)?;
252+
let (_, path_weight) = longest_path(graph, edge_weight_callable)?;
208253
Ok(path_weight)
209254
}
210255

0 commit comments

Comments
 (0)