mod astar;
mod dag_isomorphism;
mod digraph;
mod dijkstra;
mod dot_utils;
mod generators;
mod graph;
mod iterators;
mod k_shortest_path;
mod max_weight_matching;
mod union;
use std::cmp::{Ordering, Reverse};
use std::collections::{BTreeSet, BinaryHeap};
use hashbrown::{HashMap, HashSet};
use pyo3::create_exception;
use pyo3::exceptions::{PyException, PyValueError};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
use pyo3::wrap_pyfunction;
use pyo3::wrap_pymodule;
use pyo3::Python;
use petgraph::algo;
use petgraph::graph::NodeIndex;
use petgraph::prelude::*;
use petgraph::visit::{
Bfs, Data, GraphBase, GraphProp, IntoEdgeReferences, IntoNeighbors,
IntoNodeIdentifiers, NodeCount, NodeIndexable, Reversed, VisitMap,
Visitable,
};
use ndarray::prelude::*;
use numpy::IntoPyArray;
use rand::distributions::{Distribution, Uniform};
use rand::prelude::*;
use rand_pcg::Pcg64;
use rayon::prelude::*;
use crate::generators::PyInit_generators;
use crate::iterators::{EdgeList, NodeIndices};
trait NodesRemoved {
fn nodes_removed(&self) -> bool;
}
fn longest_path(graph: &digraph::PyDiGraph) -> PyResult<Vec<usize>> {
let dag = &graph.graph;
let mut path: Vec<usize> = Vec::new();
let nodes = match algo::toposort(graph, None) {
Ok(nodes) => nodes,
Err(_err) => {
return Err(DAGHasCycle::new_err("Sort encountered a cycle"))
}
};
if nodes.is_empty() {
return Ok(path);
}
let mut dist: HashMap<NodeIndex, (usize, NodeIndex)> = HashMap::new();
for node in nodes {
let parents =
dag.neighbors_directed(node, petgraph::Direction::Incoming);
let mut us: Vec<(usize, NodeIndex)> = Vec::new();
for p_node in parents {
let length = dist[&p_node].0 + 1;
us.push((length, p_node));
}
let maxu: (usize, NodeIndex);
if !us.is_empty() {
maxu = *us.iter().max_by_key(|x| x.0).unwrap();
} else {
maxu = (0, node);
};
dist.insert(node, maxu);
}
let first = match dist.keys().max_by_key(|index| dist[index]) {
Some(first) => first,
None => {
return Err(PyException::new_err(
"Encountered something unexpected",
))
}
};
let mut v = *first;
let mut u: Option<NodeIndex> = None;
while match u {
Some(u) => u != v,
None => true,
} {
path.push(v.index());
u = Some(v);
v = dist[&v].1;
}
path.reverse();
Ok(path)
}
#[pyfunction]
#[text_signature = "(graph, /)"]
fn dag_longest_path(graph: &digraph::PyDiGraph) -> PyResult<NodeIndices> {
Ok(NodeIndices {
nodes: longest_path(graph)?,
})
}
#[pyfunction]
#[text_signature = "(graph, /)"]
fn dag_longest_path_length(graph: &digraph::PyDiGraph) -> PyResult<usize> {
let path = longest_path(graph)?;
if path.is_empty() {
return Ok(0);
}
let path_length: usize = path.len() - 1;
Ok(path_length)
}
#[pyfunction]
#[text_signature = "(graph, /)"]
fn number_weakly_connected_components(graph: &digraph::PyDiGraph) -> usize {
algo::connected_components(graph)
}
#[pyfunction]
#[text_signature = "(graph, /)"]
pub fn weakly_connected_components(
graph: &digraph::PyDiGraph,
) -> Vec<BTreeSet<usize>> {
let mut seen: HashSet<NodeIndex> =
HashSet::with_capacity(graph.node_count());
let mut out_vec: Vec<BTreeSet<usize>> = Vec::new();
for node in graph.graph.node_indices() {
if !seen.contains(&node) {
let mut component_set: BTreeSet<usize> = BTreeSet::new();
let mut bfs_seen: HashSet<NodeIndex> = HashSet::new();
let mut next_level: HashSet<NodeIndex> = HashSet::new();
next_level.insert(node);
while !next_level.is_empty() {
let this_level = next_level;
next_level = HashSet::new();
for bfs_node in this_level {
if !bfs_seen.contains(&bfs_node) {
component_set.insert(bfs_node.index());
bfs_seen.insert(bfs_node);
for neighbor in
graph.graph.neighbors_undirected(bfs_node)
{
next_level.insert(neighbor);
}
}
}
}
out_vec.push(component_set);
seen.extend(bfs_seen);
}
}
out_vec
}
#[pyfunction]
#[text_signature = "(graph, /)"]
pub fn is_weakly_connected(graph: &digraph::PyDiGraph) -> PyResult<bool> {
if graph.graph.node_count() == 0 {
return Err(NullGraph::new_err("Invalid operation on a NullGraph"));
}
Ok(weakly_connected_components(graph)[0].len() == graph.graph.node_count())
}
#[pyfunction]
#[text_signature = "(graph, /)"]
fn is_directed_acyclic_graph(graph: &digraph::PyDiGraph) -> bool {
match algo::toposort(graph, None) {
Ok(_nodes) => true,
Err(_err) => false,
}
}
#[pyfunction]
#[text_signature = "(first, second, /)"]
fn is_isomorphic(
first: &digraph::PyDiGraph,
second: &digraph::PyDiGraph,
) -> PyResult<bool> {
let res = dag_isomorphism::is_isomorphic(first, second)?;
Ok(res)
}
#[pyfunction]
#[text_signature = "(first, second, merge_nodes, merge_edges, /)"]
fn digraph_union(
py: Python,
first: &digraph::PyDiGraph,
second: &digraph::PyDiGraph,
merge_nodes: bool,
merge_edges: bool,
) -> PyResult<digraph::PyDiGraph> {
let res =
union::digraph_union(py, first, second, merge_nodes, merge_edges)?;
Ok(res)
}
#[pyfunction]
#[text_signature = "(first, second, matcher, /)"]
fn is_isomorphic_node_match(
py: Python,
first: &digraph::PyDiGraph,
second: &digraph::PyDiGraph,
matcher: PyObject,
) -> PyResult<bool> {
let compare_nodes = |a: &PyObject, b: &PyObject| -> PyResult<bool> {
let res = matcher.call1(py, (a, b))?;
Ok(res.is_true(py).unwrap())
};
#[allow(clippy::unnecessary_wraps)]
fn compare_edges(_a: &PyObject, _b: &PyObject) -> PyResult<bool> {
Ok(true)
}
let res = dag_isomorphism::is_isomorphic_matching(
py,
first,
second,
compare_nodes,
compare_edges,
)?;
Ok(res)
}
#[pyfunction]
#[text_signature = "(graph, /)"]
fn topological_sort(graph: &digraph::PyDiGraph) -> PyResult<NodeIndices> {
let nodes = match algo::toposort(graph, None) {
Ok(nodes) => nodes,
Err(_err) => {
return Err(DAGHasCycle::new_err("Sort encountered a cycle"))
}
};
Ok(NodeIndices {
nodes: nodes.iter().map(|node| node.index()).collect(),
})
}
fn dfs_edges<G>(
graph: G,
source: Option<usize>,
edge_count: usize,
) -> Vec<(usize, usize)>
where
G: GraphBase<NodeId = NodeIndex>
+ IntoNodeIdentifiers
+ NodeIndexable
+ IntoNeighbors
+ NodeCount
+ Visitable,
<G as Visitable>::Map: VisitMap<NodeIndex>,
{
let nodes: Vec<NodeIndex> = match source {
Some(start) => vec![NodeIndex::new(start)],
None => graph
.node_identifiers()
.map(|ind| NodeIndex::new(graph.to_index(ind)))
.collect(),
};
let node_count = graph.node_count();
let mut visited: HashSet<NodeIndex> = HashSet::with_capacity(node_count);
let mut out_vec: Vec<(usize, usize)> = Vec::with_capacity(edge_count);
for start in nodes {
if visited.contains(&start) {
continue;
}
visited.insert(start);
let mut children: Vec<NodeIndex> = graph.neighbors(start).collect();
children.reverse();
let mut stack: Vec<(NodeIndex, Vec<NodeIndex>)> =
vec![(start, children)];
let mut index_map: HashMap<NodeIndex, usize> =
HashMap::with_capacity(node_count);
index_map.insert(start, 0);
while !stack.is_empty() {
let temp_parent = stack.last().unwrap();
let parent = temp_parent.0;
let children = temp_parent.1.clone();
let count = *index_map.get(&parent).unwrap();
let mut found = false;
let mut index = count;
for child in &children[index..] {
index += 1;
if !visited.contains(&child) {
out_vec.push((parent.index(), child.index()));
visited.insert(*child);
let mut grandchildren: Vec<NodeIndex> =
graph.neighbors(*child).collect();
grandchildren.reverse();
stack.push((*child, grandchildren));
index_map.insert(*child, 0);
*index_map.get_mut(&parent).unwrap() = index;
found = true;
break;
}
}
if !found || children.is_empty() {
stack.pop();
}
}
}
out_vec
}
#[pyfunction]
#[text_signature = "(graph, /, source=None)"]
fn digraph_dfs_edges(
graph: &digraph::PyDiGraph,
source: Option<usize>,
) -> EdgeList {
EdgeList {
edges: dfs_edges(graph, source, graph.graph.edge_count()),
}
}
#[pyfunction]
#[text_signature = "(graph, /, source=None)"]
fn graph_dfs_edges(graph: &graph::PyGraph, source: Option<usize>) -> EdgeList {
EdgeList {
edges: dfs_edges(graph, source, graph.graph.edge_count()),
}
}
#[pyfunction]
#[text_signature = "(graph, node, /)"]
fn bfs_successors(
py: Python,
graph: &digraph::PyDiGraph,
node: usize,
) -> iterators::BFSSuccessors {
let index = NodeIndex::new(node);
let mut bfs = Bfs::new(graph, index);
let mut out_list: Vec<(PyObject, Vec<PyObject>)> =
Vec::with_capacity(graph.node_count());
while let Some(nx) = bfs.next(graph) {
let children = graph
.graph
.neighbors_directed(nx, petgraph::Direction::Outgoing);
let mut succesors: Vec<PyObject> = Vec::new();
for succ in children {
succesors
.push(graph.graph.node_weight(succ).unwrap().clone_ref(py));
}
if !succesors.is_empty() {
out_list.push((
graph.graph.node_weight(nx).unwrap().clone_ref(py),
succesors,
));
}
}
iterators::BFSSuccessors {
bfs_successors: out_list,
}
}
#[pyfunction]
#[text_signature = "(graph, node, /)"]
fn ancestors(graph: &digraph::PyDiGraph, node: usize) -> HashSet<usize> {
let index = NodeIndex::new(node);
let mut out_set: HashSet<usize> = HashSet::new();
let reverse_graph = Reversed(graph);
let res = algo::dijkstra(reverse_graph, index, None, |_| 1);
for n in res.keys() {
let n_int = n.index();
out_set.insert(n_int);
}
out_set.remove(&node);
out_set
}
#[pyfunction]
#[text_signature = "(graph, node, /)"]
fn descendants(graph: &digraph::PyDiGraph, node: usize) -> HashSet<usize> {
let index = NodeIndex::new(node);
let mut out_set: HashSet<usize> = HashSet::new();
let res = algo::dijkstra(graph, index, None, |_| 1);
for n in res.keys() {
let n_int = n.index();
out_set.insert(n_int);
}
out_set.remove(&node);
out_set
}
#[pyfunction]
#[text_signature = "(dag, key, /)"]
fn lexicographical_topological_sort(
py: Python,
dag: &digraph::PyDiGraph,
key: PyObject,
) -> PyResult<PyObject> {
let key_callable = |a: &PyObject| -> PyResult<PyObject> {
let res = key.call1(py, (a,))?;
Ok(res.to_object(py))
};
let node_count = dag.node_count();
let mut in_degree_map: HashMap<NodeIndex, usize> =
HashMap::with_capacity(node_count);
for node in dag.graph.node_indices() {
in_degree_map.insert(node, dag.in_degree(node.index()));
}
#[derive(Clone, Eq, PartialEq)]
struct State {
key: String,
node: NodeIndex,
}
impl Ord for State {
fn cmp(&self, other: &State) -> Ordering {
other
.key
.cmp(&self.key)
.then_with(|| other.node.index().cmp(&self.node.index()))
}
}
impl PartialOrd for State {
fn partial_cmp(&self, other: &State) -> Option<Ordering> {
Some(self.cmp(other))
}
}
let mut zero_indegree = BinaryHeap::with_capacity(node_count);
for (node, degree) in in_degree_map.iter() {
if *degree == 0 {
let map_key_raw = key_callable(&dag.graph[*node])?;
let map_key: String = map_key_raw.extract(py)?;
zero_indegree.push(State {
key: map_key,
node: *node,
});
}
}
let mut out_list: Vec<&PyObject> = Vec::with_capacity(node_count);
let dir = petgraph::Direction::Outgoing;
while let Some(State { node, .. }) = zero_indegree.pop() {
let neighbors = dag.graph.neighbors_directed(node, dir);
for child in neighbors {
let child_degree = in_degree_map.get_mut(&child).unwrap();
*child_degree -= 1;
if *child_degree == 0 {
let map_key_raw = key_callable(&dag.graph[child])?;
let map_key: String = map_key_raw.extract(py)?;
zero_indegree.push(State {
key: map_key,
node: child,
});
in_degree_map.remove(&child);
}
}
out_list.push(&dag.graph[node])
}
Ok(PyList::new(py, out_list).into())
}
#[pyfunction]
#[text_signature = "(graph, /)"]
fn graph_greedy_color(
py: Python,
graph: &graph::PyGraph,
) -> PyResult<PyObject> {
let mut colors: HashMap<usize, usize> = HashMap::new();
let mut node_vec: Vec<NodeIndex> = graph.graph.node_indices().collect();
let mut sort_map: HashMap<NodeIndex, usize> =
HashMap::with_capacity(graph.node_count());
for k in node_vec.iter() {
sort_map.insert(*k, graph.graph.edges(*k).count());
}
node_vec.par_sort_by_key(|k| Reverse(sort_map.get(k)));
for u_index in node_vec {
let mut neighbor_colors: HashSet<usize> = HashSet::new();
for edge in graph.graph.edges(u_index) {
let target = edge.target().index();
let existing_color = match colors.get(&target) {
Some(node) => node,
None => continue,
};
neighbor_colors.insert(*existing_color);
}
let mut count: usize = 0;
loop {
if !neighbor_colors.contains(&count) {
break;
}
count += 1;
}
colors.insert(u_index.index(), count);
}
let out_dict = PyDict::new(py);
for (index, color) in colors {
out_dict.set_item(index, color)?;
}
Ok(out_dict.into())
}
#[pyfunction]
#[text_signature = "(graph, start, k, edge_cost, /, goal=None)"]
fn digraph_k_shortest_path_lengths(
py: Python,
graph: &digraph::PyDiGraph,
start: usize,
k: usize,
edge_cost: PyObject,
goal: Option<usize>,
) -> PyResult<PyObject> {
let out_goal = match goal {
Some(g) => Some(NodeIndex::new(g)),
None => None,
};
let edge_cost_callable = |edge: &PyObject| -> PyResult<f64> {
let res = edge_cost.call1(py, (edge,))?;
res.extract(py)
};
let out_map = k_shortest_path::k_shortest_path(
graph,
NodeIndex::new(start),
out_goal,
k,
edge_cost_callable,
)?;
let out_dict = PyDict::new(py);
for (index, length) in out_map {
if (out_goal.is_some() && out_goal.unwrap() == index)
|| out_goal.is_none()
{
out_dict.set_item(index.index(), length)?;
}
}
Ok(out_dict.into())
}
#[pyfunction]
#[text_signature = "(graph, start, k, edge_cost, /, goal=None)"]
fn graph_k_shortest_path_lengths(
py: Python,
graph: &graph::PyGraph,
start: usize,
k: usize,
edge_cost: PyObject,
goal: Option<usize>,
) -> PyResult<PyObject> {
let out_goal = match goal {
Some(g) => Some(NodeIndex::new(g)),
None => None,
};
let edge_cost_callable = |edge: &PyObject| -> PyResult<f64> {
let res = edge_cost.call1(py, (edge,))?;
res.extract(py)
};
let out_map = k_shortest_path::k_shortest_path(
graph,
NodeIndex::new(start),
out_goal,
k,
edge_cost_callable,
)?;
let out_dict = PyDict::new(py);
for (index, length) in out_map {
if (out_goal.is_some() && out_goal.unwrap() == index)
|| out_goal.is_none()
{
out_dict.set_item(index.index(), length)?;
}
}
Ok(out_dict.into())
}
#[pyfunction]
#[text_signature = "(dag, /)"]
fn floyd_warshall(py: Python, dag: &digraph::PyDiGraph) -> PyResult<PyObject> {
let mut dist: HashMap<(usize, usize), usize> =
HashMap::with_capacity(dag.node_count());
for node in dag.graph.node_indices() {
dist.insert((node.index(), node.index()), 0);
}
for edge in dag.graph.edge_indices() {
let source_target = dag.graph.edge_endpoints(edge).unwrap();
let u = source_target.0.index();
let v = source_target.1.index();
dist.entry((u, v)).or_insert(1);
}
for w in dag.graph.node_indices() {
for u in dag.graph.node_indices() {
for v in dag.graph.node_indices() {
let u_v_dist = match dist.get(&(u.index(), v.index())) {
Some(u_v_dist) => *u_v_dist,
None => std::usize::MAX,
};
let u_w_dist = match dist.get(&(u.index(), w.index())) {
Some(u_w_dist) => *u_w_dist,
None => std::usize::MAX,
};
let w_v_dist = match dist.get(&(w.index(), v.index())) {
Some(w_v_dist) => *w_v_dist,
None => std::usize::MAX,
};
if u_w_dist == std::usize::MAX || w_v_dist == std::usize::MAX {
continue;
}
if u_v_dist > u_w_dist + w_v_dist {
dist.insert((u.index(), v.index()), u_w_dist + w_v_dist);
}
}
}
}
let out_dict = PyDict::new(py);
for (nodes, distance) in dist {
let u_index = nodes.0;
let v_index = nodes.1;
if out_dict.contains(u_index)? {
let u_dict =
out_dict.get_item(u_index).unwrap().downcast::<PyDict>()?;
u_dict.set_item(v_index, distance)?;
out_dict.set_item(u_index, u_dict)?;
} else {
let u_dict = PyDict::new(py);
u_dict.set_item(v_index, distance)?;
out_dict.set_item(u_index, u_dict)?;
}
}
Ok(out_dict.into())
}
fn get_edge_iter_with_weights<G>(
graph: G,
) -> impl Iterator<Item = (usize, usize, PyObject)>
where
G: GraphBase
+ IntoEdgeReferences
+ IntoNodeIdentifiers
+ NodeIndexable
+ NodeCount
+ GraphProp
+ NodesRemoved,
G: Data<NodeWeight = PyObject, EdgeWeight = PyObject>,
{
let node_map: Option<HashMap<NodeIndex, usize>>;
if graph.nodes_removed() {
let mut node_hash_map: HashMap<NodeIndex, usize> =
HashMap::with_capacity(graph.node_count());
for (count, node) in graph.node_identifiers().enumerate() {
let index = NodeIndex::new(graph.to_index(node));
node_hash_map.insert(index, count);
}
node_map = Some(node_hash_map);
} else {
node_map = None;
}
graph.edge_references().map(move |edge| {
let i: usize;
let j: usize;
match &node_map {
Some(map) => {
let source_index =
NodeIndex::new(graph.to_index(edge.source()));
let target_index =
NodeIndex::new(graph.to_index(edge.target()));
i = *map.get(&source_index).unwrap();
j = *map.get(&target_index).unwrap();
}
None => {
i = graph.to_index(edge.source());
j = graph.to_index(edge.target());
}
}
(i, j, edge.weight().clone())
})
}
#[pyfunction(default_weight = "1.0")]
#[text_signature = "(graph, /, weight_fn=None, default_weight=1.0)"]
fn graph_floyd_warshall_numpy(
py: Python,
graph: &graph::PyGraph,
weight_fn: Option<PyObject>,
default_weight: f64,
) -> PyResult<PyObject> {
let n = graph.node_count();
let mut mat = Array2::<f64>::from_elem((n, n), std::f64::INFINITY);
for (i, j, weight) in get_edge_iter_with_weights(graph) {
let edge_weight =
weight_callable(py, &weight_fn, &weight, default_weight)?;
mat[[i, j]] = mat[[i, j]].min(edge_weight);
mat[[j, i]] = mat[[j, i]].min(edge_weight);
}
for x in mat.diag_mut() {
*x = 0.0;
}
for k in 0..n {
for i in 0..n {
for j in 0..n {
let d_ijk = mat[[i, k]] + mat[[k, j]];
if d_ijk < mat[[i, j]] {
mat[[i, j]] = d_ijk;
}
}
}
}
Ok(mat.into_pyarray(py).into())
}
#[pyfunction(as_undirected = "false", default_weight = "1.0")]
#[text_signature = "(graph, /, weight_fn=None as_undirected=False, default_weight=1.0)"]
fn digraph_floyd_warshall_numpy(
py: Python,
graph: &digraph::PyDiGraph,
weight_fn: Option<PyObject>,
as_undirected: bool,
default_weight: f64,
) -> PyResult<PyObject> {
let n = graph.node_count();
let mut mat = Array2::<f64>::from_elem((n, n), std::f64::INFINITY);
for (i, j, weight) in get_edge_iter_with_weights(graph) {
let edge_weight =
weight_callable(py, &weight_fn, &weight, default_weight)?;
mat[[i, j]] = mat[[i, j]].min(edge_weight);
if as_undirected {
mat[[j, i]] = mat[[j, i]].min(edge_weight);
}
}
for x in mat.diag_mut() {
*x = 0.0;
}
for k in 0..n {
for i in 0..n {
for j in 0..n {
let d_ijk = mat[[i, k]] + mat[[k, j]];
if d_ijk < mat[[i, j]] {
mat[[i, j]] = d_ijk;
}
}
}
}
Ok(mat.into_pyarray(py).into())
}
#[pyfunction]
#[text_signature = "(graph, filter)"]
fn collect_runs(
py: Python,
graph: &digraph::PyDiGraph,
filter_fn: PyObject,
) -> PyResult<Vec<Vec<PyObject>>> {
let mut out_list: Vec<Vec<PyObject>> = Vec::new();
let mut seen: HashSet<NodeIndex> =
HashSet::with_capacity(graph.node_count());
let filter_node = |node: &PyObject| -> PyResult<bool> {
let res = filter_fn.call1(py, (node,))?;
res.extract(py)
};
let nodes = match algo::toposort(graph, None) {
Ok(nodes) => nodes,
Err(_err) => {
return Err(DAGHasCycle::new_err("Sort encountered a cycle"))
}
};
for node in nodes {
if !filter_node(&graph.graph[node])? || seen.contains(&node) {
continue;
}
seen.insert(node);
let mut group: Vec<PyObject> = vec![graph.graph[node].clone_ref(py)];
let mut successors: Vec<NodeIndex> = graph
.graph
.neighbors_directed(node, petgraph::Direction::Outgoing)
.collect();
successors.dedup();
while successors.len() == 1
&& filter_node(&graph.graph[successors[0]])?
&& !seen.contains(&successors[0])
{
group.push(graph.graph[successors[0]].clone_ref(py));
seen.insert(successors[0]);
successors = graph
.graph
.neighbors_directed(
successors[0],
petgraph::Direction::Outgoing,
)
.collect();
successors.dedup();
}
if !group.is_empty() {
out_list.push(group);
}
}
Ok(out_list)
}
#[pyfunction]
#[text_signature = "(dag, first_layer, /)"]
fn layers(
py: Python,
dag: &digraph::PyDiGraph,
first_layer: Vec<usize>,
) -> PyResult<PyObject> {
let mut output: Vec<Vec<&PyObject>> = Vec::new();
let mut first_layer_index: Vec<NodeIndex> = Vec::new();
for index in first_layer {
first_layer_index.push(NodeIndex::new(index));
}
let mut cur_layer = first_layer_index;
let mut next_layer: Vec<NodeIndex> = Vec::new();
let mut predecessor_count: HashMap<NodeIndex, usize> = HashMap::new();
let mut layer_node_data: Vec<&PyObject> = Vec::new();
for layer_node in &cur_layer {
let node_data = match dag.graph.node_weight(*layer_node) {
Some(data) => data,
None => {
return Err(InvalidNode::new_err(format!(
"An index input in 'first_layer' {} is not a valid node index in the graph",
layer_node.index()),
))
}
};
layer_node_data.push(node_data);
}
output.push(layer_node_data);
while !cur_layer.is_empty() {
for node in &cur_layer {
let children = dag
.graph
.neighbors_directed(*node, petgraph::Direction::Outgoing);
let mut used_indexes: HashSet<NodeIndex> = HashSet::new();
for succ in children {
if used_indexes.contains(&succ) {
continue;
}
used_indexes.insert(succ);
let mut multiplicity: usize = 0;
let raw_edges = dag
.graph
.edges_directed(*node, petgraph::Direction::Outgoing);
for edge in raw_edges {
if edge.target() == succ {
multiplicity += 1;
}
}
predecessor_count
.entry(succ)
.and_modify(|e| *e -= multiplicity)
.or_insert(dag.in_degree(succ.index()) - multiplicity);
if *predecessor_count.get(&succ).unwrap() == 0 {
next_layer.push(succ);
predecessor_count.remove(&succ);
}
}
}
let mut layer_node_data: Vec<&PyObject> = Vec::new();
for layer_node in &next_layer {
layer_node_data.push(&dag[*layer_node]);
}
if !layer_node_data.is_empty() {
output.push(layer_node_data);
}
cur_layer = next_layer;
next_layer = Vec::new();
}
Ok(PyList::new(py, output).into())
}
#[pyfunction(parallel_threshold = "300", as_undirected = "false")]
#[text_signature = "(graph, /, parallel_threshold=300, as_undirected=False)"]
pub fn digraph_distance_matrix(
py: Python,
graph: &digraph::PyDiGraph,
parallel_threshold: usize,
as_undirected: bool,
) -> PyResult<PyObject> {
let n = graph.node_count();
let mut matrix = Array2::<f64>::zeros((n, n));
let bfs_traversal = |index: usize, mut row: ArrayViewMut1<f64>| {
let mut seen: HashMap<NodeIndex, usize> = HashMap::with_capacity(n);
let start_index = NodeIndex::new(index);
let mut level = 0;
let mut next_level: HashSet<NodeIndex> = HashSet::new();
next_level.insert(start_index);
while !next_level.is_empty() {
let this_level = next_level;
next_level = HashSet::new();
let mut found: Vec<NodeIndex> = Vec::new();
for v in this_level {
if !seen.contains_key(&v) {
seen.insert(v, level);
found.push(v);
row[[v.index()]] = level as f64;
}
}
if seen.len() == n {
return;
}
for node in found {
for v in graph
.graph
.neighbors_directed(node, petgraph::Direction::Outgoing)
{
next_level.insert(v);
}
if as_undirected {
for v in graph
.graph
.neighbors_directed(node, petgraph::Direction::Incoming)
{
next_level.insert(v);
}
}
}
level += 1
}
};
if n < parallel_threshold {
matrix
.axis_iter_mut(Axis(0))
.enumerate()
.for_each(|(index, row)| bfs_traversal(index, row));
} else {
matrix
.axis_iter_mut(Axis(0))
.into_par_iter()
.enumerate()
.for_each(|(index, row)| bfs_traversal(index, row));
}
Ok(matrix.into_pyarray(py).into())
}
#[pyfunction(parallel_threshold = "300")]
#[text_signature = "(graph, /, parallel_threshold=300)"]
pub fn graph_distance_matrix(
py: Python,
graph: &graph::PyGraph,
parallel_threshold: usize,
) -> PyResult<PyObject> {
let n = graph.node_count();
let mut matrix = Array2::<f64>::zeros((n, n));
let bfs_traversal = |index: usize, mut row: ArrayViewMut1<f64>| {
let mut seen: HashMap<NodeIndex, usize> = HashMap::with_capacity(n);
let start_index = NodeIndex::new(index);
let mut level = 0;
let mut next_level: HashSet<NodeIndex> = HashSet::new();
next_level.insert(start_index);
while !next_level.is_empty() {
let this_level = next_level;
next_level = HashSet::new();
let mut found: Vec<NodeIndex> = Vec::new();
for v in this_level {
if !seen.contains_key(&v) {
seen.insert(v, level);
found.push(v);
row[[v.index()]] = level as f64;
}
}
if seen.len() == n {
return;
}
for node in found {
for v in graph.graph.neighbors(node) {
next_level.insert(v);
}
}
level += 1
}
};
if n < parallel_threshold {
matrix
.axis_iter_mut(Axis(0))
.enumerate()
.for_each(|(index, row)| bfs_traversal(index, row));
} else {
matrix
.axis_iter_mut(Axis(0))
.into_par_iter()
.enumerate()
.for_each(|(index, row)| bfs_traversal(index, row));
}
Ok(matrix.into_pyarray(py).into())
}
#[pyfunction(default_weight = "1.0")]
#[text_signature = "(graph, /, weight_fn=None, default_weight=1.0)"]
fn digraph_adjacency_matrix(
py: Python,
graph: &digraph::PyDiGraph,
weight_fn: Option<PyObject>,
default_weight: f64,
) -> PyResult<PyObject> {
let n = graph.node_count();
let mut matrix = Array2::<f64>::zeros((n, n));
for (i, j, weight) in get_edge_iter_with_weights(graph) {
let edge_weight =
weight_callable(py, &weight_fn, &weight, default_weight)?;
matrix[[i, j]] += edge_weight;
}
Ok(matrix.into_pyarray(py).into())
}
#[pyfunction(default_weight = "1.0")]
#[text_signature = "(graph, /, weight_fn=None, default_weight=1.0)"]
fn graph_adjacency_matrix(
py: Python,
graph: &graph::PyGraph,
weight_fn: Option<PyObject>,
default_weight: f64,
) -> PyResult<PyObject> {
let n = graph.node_count();
let mut matrix = Array2::<f64>::zeros((n, n));
for (i, j, weight) in get_edge_iter_with_weights(graph) {
let edge_weight =
weight_callable(py, &weight_fn, &weight, default_weight)?;
matrix[[i, j]] += edge_weight;
matrix[[j, i]] += edge_weight;
}
Ok(matrix.into_pyarray(py).into())
}
#[pyfunction]
#[text_signature = "(graph, from, to, /, min=None, cutoff=None)"]
fn graph_all_simple_paths(
graph: &graph::PyGraph,
from: usize,
to: usize,
min_depth: Option<usize>,
cutoff: Option<usize>,
) -> PyResult<Vec<Vec<usize>>> {
let from_index = NodeIndex::new(from);
if !graph.graph.contains_node(from_index) {
return Err(InvalidNode::new_err(
"The input index for 'from' is not a valid node index",
));
}
let to_index = NodeIndex::new(to);
if !graph.graph.contains_node(to_index) {
return Err(InvalidNode::new_err(
"The input index for 'to' is not a valid node index",
));
}
let min_intermediate_nodes: usize = match min_depth {
Some(depth) => depth - 2,
None => 0,
};
let cutoff_petgraph: Option<usize> = match cutoff {
Some(depth) => Some(depth - 2),
None => None,
};
let result: Vec<Vec<usize>> = algo::all_simple_paths(
graph,
from_index,
to_index,
min_intermediate_nodes,
cutoff_petgraph,
)
.map(|v: Vec<NodeIndex>| v.into_iter().map(|i| i.index()).collect())
.collect();
Ok(result)
}
#[pyfunction]
#[text_signature = "(graph, from, to, /, min_depth=None, cutoff=None)"]
fn digraph_all_simple_paths(
graph: &digraph::PyDiGraph,
from: usize,
to: usize,
min_depth: Option<usize>,
cutoff: Option<usize>,
) -> PyResult<Vec<Vec<usize>>> {
let from_index = NodeIndex::new(from);
if !graph.graph.contains_node(from_index) {
return Err(InvalidNode::new_err(
"The input index for 'from' is not a valid node index",
));
}
let to_index = NodeIndex::new(to);
if !graph.graph.contains_node(to_index) {
return Err(InvalidNode::new_err(
"The input index for 'to' is not a valid node index",
));
}
let min_intermediate_nodes: usize = match min_depth {
Some(depth) => depth - 2,
None => 0,
};
let cutoff_petgraph: Option<usize> = match cutoff {
Some(depth) => Some(depth - 2),
None => None,
};
let result: Vec<Vec<usize>> = algo::all_simple_paths(
graph,
from_index,
to_index,
min_intermediate_nodes,
cutoff_petgraph,
)
.map(|v: Vec<NodeIndex>| v.into_iter().map(|i| i.index()).collect())
.collect();
Ok(result)
}
fn weight_callable(
py: Python,
weight_fn: &Option<PyObject>,
weight: &PyObject,
default: f64,
) -> PyResult<f64> {
match weight_fn {
Some(weight_fn) => {
let res = weight_fn.call1(py, (weight,))?;
res.extract(py)
}
None => Ok(default),
}
}
#[pyfunction(default_weight = "1.0", as_undirected = "false")]
#[text_signature = "(graph, source, /, target=None weight_fn=None, default_weight=1.0)"]
pub fn graph_dijkstra_shortest_paths(
py: Python,
graph: &graph::PyGraph,
source: usize,
target: Option<usize>,
weight_fn: Option<PyObject>,
default_weight: f64,
) -> PyResult<PyObject> {
let start = NodeIndex::new(source);
let goal_index: Option<NodeIndex> = match target {
Some(node) => Some(NodeIndex::new(node)),
None => None,
};
let mut paths: HashMap<NodeIndex, Vec<NodeIndex>> =
HashMap::with_capacity(graph.node_count());
dijkstra::dijkstra(
graph,
start,
goal_index,
|e| weight_callable(py, &weight_fn, e.weight(), default_weight),
Some(&mut paths),
)?;
let out_dict = PyDict::new(py);
for (index, value) in paths {
let int_index = index.index();
if int_index == source {
continue;
}
if (target.is_some() && target.unwrap() == int_index)
|| target.is_none()
{
out_dict.set_item(
int_index,
value
.iter()
.map(|index| index.index())
.collect::<Vec<usize>>(),
)?;
}
}
Ok(out_dict.into())
}
#[pyfunction(default_weight = "1.0", as_undirected = "false")]
#[text_signature = "(graph, source, /, target=None weight_fn=None, default_weight=1.0, as_undirected=False)"]
pub fn digraph_dijkstra_shortest_paths(
py: Python,
graph: &digraph::PyDiGraph,
source: usize,
target: Option<usize>,
weight_fn: Option<PyObject>,
default_weight: f64,
as_undirected: bool,
) -> PyResult<PyObject> {
let start = NodeIndex::new(source);
let goal_index: Option<NodeIndex> = match target {
Some(node) => Some(NodeIndex::new(node)),
None => None,
};
let mut paths: HashMap<NodeIndex, Vec<NodeIndex>> =
HashMap::with_capacity(graph.node_count());
if as_undirected {
dijkstra::dijkstra(
&graph.to_undirected(py),
start,
goal_index,
|e| weight_callable(py, &weight_fn, e.weight(), default_weight),
Some(&mut paths),
)?;
} else {
dijkstra::dijkstra(
graph,
start,
goal_index,
|e| weight_callable(py, &weight_fn, e.weight(), default_weight),
Some(&mut paths),
)?;
}
let out_dict = PyDict::new(py);
for (index, value) in paths {
let int_index = index.index();
if int_index == source {
continue;
}
if (target.is_some() && target.unwrap() == int_index)
|| target.is_none()
{
out_dict.set_item(
int_index,
value
.iter()
.map(|index| index.index())
.collect::<Vec<usize>>(),
)?;
}
}
Ok(out_dict.into())
}
#[pyfunction]
#[text_signature = "(graph, node, edge_cost_fn, /, goal=None)"]
fn graph_dijkstra_shortest_path_lengths(
py: Python,
graph: &graph::PyGraph,
node: usize,
edge_cost_fn: PyObject,
goal: Option<usize>,
) -> PyResult<PyObject> {
let edge_cost_callable = |a: &PyObject| -> PyResult<f64> {
let res = edge_cost_fn.call1(py, (a,))?;
let raw = res.to_object(py);
raw.extract(py)
};
let start = NodeIndex::new(node);
let goal_index: Option<NodeIndex> = match goal {
Some(node) => Some(NodeIndex::new(node)),
None => None,
};
let res = dijkstra::dijkstra(
graph,
start,
goal_index,
|e| edge_cost_callable(e.weight()),
None,
)?;
let out_dict = PyDict::new(py);
for (index, value) in res {
let int_index = index.index();
if int_index == node {
continue;
}
if (goal.is_some() && goal.unwrap() == int_index) || goal.is_none() {
out_dict.set_item(int_index, value)?;
}
}
Ok(out_dict.into())
}
#[pyfunction]
#[text_signature = "(graph, node, edge_cost_fn, /, goal=None)"]
fn digraph_dijkstra_shortest_path_lengths(
py: Python,
graph: &digraph::PyDiGraph,
node: usize,
edge_cost_fn: PyObject,
goal: Option<usize>,
) -> PyResult<PyObject> {
let edge_cost_callable = |a: &PyObject| -> PyResult<f64> {
let res = edge_cost_fn.call1(py, (a,))?;
let raw = res.to_object(py);
raw.extract(py)
};
let start = NodeIndex::new(node);
let goal_index: Option<NodeIndex> = match goal {
Some(node) => Some(NodeIndex::new(node)),
None => None,
};
let res = dijkstra::dijkstra(
graph,
start,
goal_index,
|e| edge_cost_callable(e.weight()),
None,
)?;
let out_dict = PyDict::new(py);
for (index, value) in res {
let int_index = index.index();
if int_index == node {
continue;
}
if (goal.is_some() && goal.unwrap() == int_index) || goal.is_none() {
out_dict.set_item(int_index, value)?;
}
}
Ok(out_dict.into())
}
#[pyfunction]
#[text_signature = "(graph, node, goal_fn, edge_cost, estimate_cost, /)"]
fn graph_astar_shortest_path(
py: Python,
graph: &graph::PyGraph,
node: usize,
goal_fn: PyObject,
edge_cost_fn: PyObject,
estimate_cost_fn: PyObject,
) -> PyResult<NodeIndices> {
let goal_fn_callable = |a: &PyObject| -> PyResult<bool> {
let res = goal_fn.call1(py, (a,))?;
let raw = res.to_object(py);
let output: bool = raw.extract(py)?;
Ok(output)
};
let edge_cost_callable = |a: &PyObject| -> PyResult<f64> {
let res = edge_cost_fn.call1(py, (a,))?;
let raw = res.to_object(py);
let output: f64 = raw.extract(py)?;
Ok(output)
};
let estimate_cost_callable = |a: &PyObject| -> PyResult<f64> {
let res = estimate_cost_fn.call1(py, (a,))?;
let raw = res.to_object(py);
let output: f64 = raw.extract(py)?;
Ok(output)
};
let start = NodeIndex::new(node);
let astar_res = astar::astar(
graph,
start,
|f| goal_fn_callable(graph.graph.node_weight(f).unwrap()),
|e| edge_cost_callable(e.weight()),
|estimate| {
estimate_cost_callable(graph.graph.node_weight(estimate).unwrap())
},
)?;
let path = match astar_res {
Some(path) => path,
None => {
return Err(NoPathFound::new_err(
"No path found that satisfies goal_fn",
))
}
};
Ok(NodeIndices {
nodes: path.1.into_iter().map(|x| x.index()).collect(),
})
}
#[pyfunction]
#[text_signature = "(graph, node, goal_fn, edge_cost, estimate_cost, /)"]
fn digraph_astar_shortest_path(
py: Python,
graph: &digraph::PyDiGraph,
node: usize,
goal_fn: PyObject,
edge_cost_fn: PyObject,
estimate_cost_fn: PyObject,
) -> PyResult<NodeIndices> {
let goal_fn_callable = |a: &PyObject| -> PyResult<bool> {
let res = goal_fn.call1(py, (a,))?;
let raw = res.to_object(py);
let output: bool = raw.extract(py)?;
Ok(output)
};
let edge_cost_callable = |a: &PyObject| -> PyResult<f64> {
let res = edge_cost_fn.call1(py, (a,))?;
let raw = res.to_object(py);
let output: f64 = raw.extract(py)?;
Ok(output)
};
let estimate_cost_callable = |a: &PyObject| -> PyResult<f64> {
let res = estimate_cost_fn.call1(py, (a,))?;
let raw = res.to_object(py);
let output: f64 = raw.extract(py)?;
Ok(output)
};
let start = NodeIndex::new(node);
let astar_res = astar::astar(
graph,
start,
|f| goal_fn_callable(graph.graph.node_weight(f).unwrap()),
|e| edge_cost_callable(e.weight()),
|estimate| {
estimate_cost_callable(graph.graph.node_weight(estimate).unwrap())
},
)?;
let path = match astar_res {
Some(path) => path,
None => {
return Err(NoPathFound::new_err(
"No path found that satisfies goal_fn",
))
}
};
Ok(NodeIndices {
nodes: path.1.into_iter().map(|x| x.index()).collect(),
})
}
#[pyfunction]
#[text_signature = "(num_nodes, probability, seed=None, /)"]
pub fn directed_gnp_random_graph(
py: Python,
num_nodes: isize,
probability: f64,
seed: Option<u64>,
) -> PyResult<digraph::PyDiGraph> {
if num_nodes <= 0 {
return Err(PyValueError::new_err("num_nodes must be > 0"));
}
let mut rng: Pcg64 = match seed {
Some(seed) => Pcg64::seed_from_u64(seed),
None => Pcg64::from_entropy(),
};
let mut inner_graph = StableDiGraph::<PyObject, PyObject>::new();
for x in 0..num_nodes {
inner_graph.add_node(x.to_object(py));
}
if !(0.0..=1.0).contains(&probability) {
return Err(PyValueError::new_err(
"Probability out of range, must be 0 <= p <= 1",
));
}
if probability > 0.0 {
if (probability - 1.0).abs() < std::f64::EPSILON {
for u in 0..num_nodes {
for v in 0..num_nodes {
if u != v {
let u_index = NodeIndex::new(u as usize);
let v_index = NodeIndex::new(v as usize);
inner_graph.add_edge(u_index, v_index, py.None());
}
}
}
} else {
let mut v: isize = 0;
let mut w: isize = -1;
let lp: f64 = (1.0 - probability).ln();
let between = Uniform::new(0.0, 1.0);
while v < num_nodes {
let random: f64 = between.sample(&mut rng);
let lr: f64 = (1.0 - random).ln();
let ratio: isize = (lr / lp) as isize;
w = w + 1 + ratio;
if v == w {
w += 1;
}
while v < num_nodes && num_nodes <= w {
w -= v;
v += 1;
if v == w {
w -= v;
v += 1;
}
}
if v < num_nodes {
let v_index = NodeIndex::new(v as usize);
let w_index = NodeIndex::new(w as usize);
inner_graph.add_edge(v_index, w_index, py.None());
}
}
}
}
let graph = digraph::PyDiGraph {
graph: inner_graph,
cycle_state: algo::DfsSpace::default(),
check_cycle: false,
node_removed: false,
multigraph: true,
};
Ok(graph)
}
#[pyfunction]
#[text_signature = "(num_nodes, probability, seed=None, /)"]
pub fn undirected_gnp_random_graph(
py: Python,
num_nodes: isize,
probability: f64,
seed: Option<u64>,
) -> PyResult<graph::PyGraph> {
if num_nodes <= 0 {
return Err(PyValueError::new_err("num_nodes must be > 0"));
}
let mut rng: Pcg64 = match seed {
Some(seed) => Pcg64::seed_from_u64(seed),
None => Pcg64::from_entropy(),
};
let mut inner_graph = StableUnGraph::<PyObject, PyObject>::default();
for x in 0..num_nodes {
inner_graph.add_node(x.to_object(py));
}
if !(0.0..=1.0).contains(&probability) {
return Err(PyValueError::new_err(
"Probability out of range, must be 0 <= p <= 1",
));
}
if probability > 0.0 {
if (probability - 1.0).abs() < std::f64::EPSILON {
for u in 0..num_nodes {
for v in u + 1..num_nodes {
let u_index = NodeIndex::new(u as usize);
let v_index = NodeIndex::new(v as usize);
inner_graph.add_edge(u_index, v_index, py.None());
}
}
} else {
let mut v: isize = 1;
let mut w: isize = -1;
let lp: f64 = (1.0 - probability).ln();
let between = Uniform::new(0.0, 1.0);
while v < num_nodes {
let random: f64 = between.sample(&mut rng);
let lr = (1.0 - random).ln();
let ratio: isize = (lr / lp) as isize;
w = w + 1 + ratio;
while w >= v && v < num_nodes {
w -= v;
v += 1;
}
if v < num_nodes {
let v_index = NodeIndex::new(v as usize);
let w_index = NodeIndex::new(w as usize);
inner_graph.add_edge(v_index, w_index, py.None());
}
}
}
}
let graph = graph::PyGraph {
graph: inner_graph,
node_removed: false,
multigraph: true,
};
Ok(graph)
}
#[pyfunction]
#[text_signature = "(num_nodes, num_edges, seed=None, /)"]
pub fn directed_gnm_random_graph(
py: Python,
num_nodes: isize,
num_edges: isize,
seed: Option<u64>,
) -> PyResult<digraph::PyDiGraph> {
if num_nodes <= 0 {
return Err(PyValueError::new_err("num_nodes must be > 0"));
}
if num_edges < 0 {
return Err(PyValueError::new_err("num_edges must be >= 0"));
}
let mut rng: Pcg64 = match seed {
Some(seed) => Pcg64::seed_from_u64(seed),
None => Pcg64::from_entropy(),
};
let mut inner_graph = StableDiGraph::<PyObject, PyObject>::new();
for x in 0..num_nodes {
inner_graph.add_node(x.to_object(py));
}
if num_edges >= num_nodes * (num_nodes - 1) {
for u in 0..num_nodes {
for v in 0..num_nodes {
if u != v {
let u_index = NodeIndex::new(u as usize);
let v_index = NodeIndex::new(v as usize);
inner_graph.add_edge(u_index, v_index, py.None());
}
}
}
} else {
let mut created_edges: isize = 0;
let between = Uniform::new(0, num_nodes);
while created_edges < num_edges {
let u = between.sample(&mut rng);
let v = between.sample(&mut rng);
let u_index = NodeIndex::new(u as usize);
let v_index = NodeIndex::new(v as usize);
if u != v && inner_graph.find_edge(u_index, v_index).is_none() {
inner_graph.add_edge(u_index, v_index, py.None());
created_edges += 1;
}
}
}
let graph = digraph::PyDiGraph {
graph: inner_graph,
cycle_state: algo::DfsSpace::default(),
check_cycle: false,
node_removed: false,
multigraph: true,
};
Ok(graph)
}
#[pyfunction]
#[text_signature = "(num_nodes, probability, seed=None, /)"]
pub fn undirected_gnm_random_graph(
py: Python,
num_nodes: isize,
num_edges: isize,
seed: Option<u64>,
) -> PyResult<graph::PyGraph> {
if num_nodes <= 0 {
return Err(PyValueError::new_err("num_nodes must be > 0"));
}
if num_edges < 0 {
return Err(PyValueError::new_err("num_edges must be >= 0"));
}
let mut rng: Pcg64 = match seed {
Some(seed) => Pcg64::seed_from_u64(seed),
None => Pcg64::from_entropy(),
};
let mut inner_graph = StableUnGraph::<PyObject, PyObject>::default();
for x in 0..num_nodes {
inner_graph.add_node(x.to_object(py));
}
if num_edges >= num_nodes * (num_nodes - 1) / 2 {
for u in 0..num_nodes {
for v in u + 1..num_nodes {
let u_index = NodeIndex::new(u as usize);
let v_index = NodeIndex::new(v as usize);
inner_graph.add_edge(u_index, v_index, py.None());
}
}
} else {
let mut created_edges: isize = 0;
let between = Uniform::new(0, num_nodes);
while created_edges < num_edges {
let u = between.sample(&mut rng);
let v = between.sample(&mut rng);
let u_index = NodeIndex::new(u as usize);
let v_index = NodeIndex::new(v as usize);
if u != v && inner_graph.find_edge(u_index, v_index).is_none() {
inner_graph.add_edge(u_index, v_index, py.None());
created_edges += 1;
}
}
}
let graph = graph::PyGraph {
graph: inner_graph,
node_removed: false,
multigraph: true,
};
Ok(graph)
}
#[pyfunction]
#[text_signature = "(graph, /, root=None)"]
pub fn cycle_basis(
graph: &graph::PyGraph,
root: Option<usize>,
) -> Vec<Vec<usize>> {
let mut root_node = root;
let mut graph_nodes: HashSet<NodeIndex> =
graph.graph.node_indices().collect();
let mut cycles: Vec<Vec<usize>> = Vec::new();
while !graph_nodes.is_empty() {
let temp_value: NodeIndex;
let root_index = match root_node {
Some(root_value) => NodeIndex::new(root_value),
None => {
temp_value = *graph_nodes.iter().next().unwrap();
graph_nodes.remove(&temp_value);
temp_value
}
};
let mut stack: Vec<NodeIndex> = vec![root_index];
let mut pred: HashMap<NodeIndex, NodeIndex> = HashMap::new();
pred.insert(root_index, root_index);
let mut used: HashMap<NodeIndex, HashSet<NodeIndex>> = HashMap::new();
used.insert(root_index, HashSet::new());
while !stack.is_empty() {
let z = stack.pop().unwrap();
for neighbor in graph.graph.neighbors(z) {
if !used.contains_key(&neighbor) {
pred.insert(neighbor, z);
stack.push(neighbor);
let mut temp_set: HashSet<NodeIndex> = HashSet::new();
temp_set.insert(z);
used.insert(neighbor, temp_set);
} else if z == neighbor {
let cycle: Vec<usize> = vec![z.index()];
cycles.push(cycle);
} else if !used.get(&z).unwrap().contains(&neighbor) {
let pn = used.get(&neighbor).unwrap();
let mut cycle: Vec<NodeIndex> = vec![neighbor, z];
let mut p = pred.get(&z).unwrap();
while !pn.contains(p) {
cycle.push(*p);
p = pred.get(p).unwrap();
}
cycle.push(*p);
cycles.push(cycle.iter().map(|x| x.index()).collect());
let neighbor_set = used.get_mut(&neighbor).unwrap();
neighbor_set.insert(z);
}
}
}
let mut temp_hashset: HashSet<NodeIndex> = HashSet::new();
for key in pred.keys() {
temp_hashset.insert(*key);
}
graph_nodes = graph_nodes.difference(&temp_hashset).copied().collect();
root_node = None;
}
cycles
}
#[pyfunction(
max_cardinality = "false",
default_weight = 1,
verify_optimum = "false"
)]
#[text_signature = "(graph, /, max_cardinality=False, weight_fn=None, default_weight=1, verify_optimum=False)"]
pub fn max_weight_matching(
py: Python,
graph: &graph::PyGraph,
max_cardinality: bool,
weight_fn: Option<PyObject>,
default_weight: i128,
verify_optimum: bool,
) -> PyResult<HashSet<(usize, usize)>> {
max_weight_matching::max_weight_matching(
py,
graph,
max_cardinality,
weight_fn,
default_weight,
verify_optimum,
)
}
#[pyfunction]
#[text_signature = "(graph, /)"]
pub fn strongly_connected_components(
graph: &digraph::PyDiGraph,
) -> Vec<Vec<usize>> {
algo::kosaraju_scc(graph)
.iter()
.map(|x| x.iter().map(|id| id.index()).collect())
.collect()
}
#[pyfunction]
#[text_signature = "(graph, /, source=None)"]
pub fn digraph_find_cycle(
graph: &digraph::PyDiGraph,
source: Option<usize>,
) -> EdgeList {
let mut graph_nodes: HashSet<NodeIndex> =
graph.graph.node_indices().collect();
let mut cycle: Vec<(usize, usize)> =
Vec::with_capacity(graph.graph.edge_count());
let temp_value: NodeIndex;
let source_index = match source {
Some(source_value) => NodeIndex::new(source_value),
None => {
temp_value = *graph_nodes.iter().next().unwrap();
graph_nodes.remove(&temp_value);
temp_value
}
};
let mut stack: Vec<NodeIndex> = vec![source_index];
let mut pred: HashMap<NodeIndex, NodeIndex> = HashMap::new();
let mut visiting = HashSet::new();
let mut visited = HashSet::new();
while !stack.is_empty() {
let mut z = *stack.last().unwrap();
visiting.insert(z);
let children = graph
.graph
.neighbors_directed(z, petgraph::Direction::Outgoing);
for child in children {
if visiting.contains(&child) {
cycle.push((z.index(), child.index()));
loop {
if z == child {
cycle.reverse();
break;
}
cycle.push((pred[&z].index(), z.index()));
z = pred[&z];
}
return EdgeList { edges: cycle };
}
if !visited.contains(&child) {
stack.push(child);
pred.insert(child, z);
}
}
let top = *stack.last().unwrap();
if top.index() == z.index() {
stack.pop();
visiting.remove(&z);
visited.insert(z);
}
}
EdgeList { edges: cycle }
}
fn _inner_is_matching(
graph: &graph::PyGraph,
matching: &HashSet<(usize, usize)>,
) -> bool {
let has_edge = |e: &(usize, usize)| -> bool {
graph
.graph
.contains_edge(NodeIndex::new(e.0), NodeIndex::new(e.1))
};
if !matching.iter().all(|e| has_edge(e)) {
return false;
}
let mut found: HashSet<usize> = HashSet::with_capacity(2 * matching.len());
for (v1, v2) in matching {
if found.contains(v1) || found.contains(v2) {
return false;
}
found.insert(*v1);
found.insert(*v2);
}
true
}
#[pyfunction]
#[text_signature = "(graph, matching, /)"]
pub fn is_matching(
graph: &graph::PyGraph,
matching: HashSet<(usize, usize)>,
) -> bool {
_inner_is_matching(graph, &matching)
}
#[pyfunction]
#[text_signature = "(graph, matching, /)"]
pub fn is_maximal_matching(
graph: &graph::PyGraph,
matching: HashSet<(usize, usize)>,
) -> bool {
if !_inner_is_matching(graph, &matching) {
return false;
}
let edge_list: HashSet<[usize; 2]> = graph
.edge_references()
.map(|edge| {
let mut tmp_array = [edge.source().index(), edge.target().index()];
tmp_array.sort_unstable();
tmp_array
})
.collect();
let matched_edges: HashSet<[usize; 2]> = matching
.iter()
.map(|edge| {
let mut tmp_array = [edge.0, edge.1];
tmp_array.sort_unstable();
tmp_array
})
.collect();
let mut unmatched_edges = edge_list.difference(&matched_edges);
unmatched_edges.all(|e| {
let mut tmp_set = matching.clone();
tmp_set.insert((e[0], e[1]));
!_inner_is_matching(graph, &tmp_set)
})
}
create_exception!(retworkx, InvalidNode, PyException);
create_exception!(retworkx, DAGWouldCycle, PyException);
create_exception!(retworkx, NoEdgeBetweenNodes, PyException);
create_exception!(retworkx, DAGHasCycle, PyException);
create_exception!(retworkx, NoSuitableNeighbors, PyException);
create_exception!(retworkx, NullGraph, PyException);
create_exception!(retworkx, NoPathFound, PyException);
#[pymodule]
fn retworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add("InvalidNode", py.get_type::<InvalidNode>())?;
m.add("DAGWouldCycle", py.get_type::<DAGWouldCycle>())?;
m.add("NoEdgeBetweenNodes", py.get_type::<NoEdgeBetweenNodes>())?;
m.add("DAGHasCycle", py.get_type::<DAGHasCycle>())?;
m.add("NoSuitableNeighbors", py.get_type::<NoSuitableNeighbors>())?;
m.add("NoPathFound", py.get_type::<NoPathFound>())?;
m.add("NullGraph", py.get_type::<NullGraph>())?;
m.add_wrapped(wrap_pyfunction!(bfs_successors))?;
m.add_wrapped(wrap_pyfunction!(dag_longest_path))?;
m.add_wrapped(wrap_pyfunction!(dag_longest_path_length))?;
m.add_wrapped(wrap_pyfunction!(number_weakly_connected_components))?;
m.add_wrapped(wrap_pyfunction!(weakly_connected_components))?;
m.add_wrapped(wrap_pyfunction!(is_weakly_connected))?;
m.add_wrapped(wrap_pyfunction!(is_directed_acyclic_graph))?;
m.add_wrapped(wrap_pyfunction!(is_isomorphic))?;
m.add_wrapped(wrap_pyfunction!(digraph_union))?;
m.add_wrapped(wrap_pyfunction!(is_isomorphic_node_match))?;
m.add_wrapped(wrap_pyfunction!(topological_sort))?;
m.add_wrapped(wrap_pyfunction!(descendants))?;
m.add_wrapped(wrap_pyfunction!(ancestors))?;
m.add_wrapped(wrap_pyfunction!(lexicographical_topological_sort))?;
m.add_wrapped(wrap_pyfunction!(floyd_warshall))?;
m.add_wrapped(wrap_pyfunction!(graph_floyd_warshall_numpy))?;
m.add_wrapped(wrap_pyfunction!(digraph_floyd_warshall_numpy))?;
m.add_wrapped(wrap_pyfunction!(collect_runs))?;
m.add_wrapped(wrap_pyfunction!(layers))?;
m.add_wrapped(wrap_pyfunction!(graph_distance_matrix))?;
m.add_wrapped(wrap_pyfunction!(digraph_distance_matrix))?;
m.add_wrapped(wrap_pyfunction!(digraph_adjacency_matrix))?;
m.add_wrapped(wrap_pyfunction!(graph_adjacency_matrix))?;
m.add_wrapped(wrap_pyfunction!(graph_all_simple_paths))?;
m.add_wrapped(wrap_pyfunction!(digraph_all_simple_paths))?;
m.add_wrapped(wrap_pyfunction!(graph_dijkstra_shortest_paths))?;
m.add_wrapped(wrap_pyfunction!(digraph_dijkstra_shortest_paths))?;
m.add_wrapped(wrap_pyfunction!(graph_dijkstra_shortest_path_lengths))?;
m.add_wrapped(wrap_pyfunction!(digraph_dijkstra_shortest_path_lengths))?;
m.add_wrapped(wrap_pyfunction!(graph_astar_shortest_path))?;
m.add_wrapped(wrap_pyfunction!(digraph_astar_shortest_path))?;
m.add_wrapped(wrap_pyfunction!(graph_greedy_color))?;
m.add_wrapped(wrap_pyfunction!(directed_gnp_random_graph))?;
m.add_wrapped(wrap_pyfunction!(undirected_gnp_random_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_gnm_random_graph))?;
m.add_wrapped(wrap_pyfunction!(undirected_gnm_random_graph))?;
m.add_wrapped(wrap_pyfunction!(cycle_basis))?;
m.add_wrapped(wrap_pyfunction!(strongly_connected_components))?;
m.add_wrapped(wrap_pyfunction!(digraph_dfs_edges))?;
m.add_wrapped(wrap_pyfunction!(graph_dfs_edges))?;
m.add_wrapped(wrap_pyfunction!(digraph_find_cycle))?;
m.add_wrapped(wrap_pyfunction!(digraph_k_shortest_path_lengths))?;
m.add_wrapped(wrap_pyfunction!(graph_k_shortest_path_lengths))?;
m.add_wrapped(wrap_pyfunction!(is_matching))?;
m.add_wrapped(wrap_pyfunction!(is_maximal_matching))?;
m.add_wrapped(wrap_pyfunction!(max_weight_matching))?;
m.add_class::<digraph::PyDiGraph>()?;
m.add_class::<graph::PyGraph>()?;
m.add_class::<iterators::BFSSuccessors>()?;
m.add_class::<iterators::NodeIndices>()?;
m.add_class::<iterators::EdgeList>()?;
m.add_class::<iterators::WeightedEdgeList>()?;
m.add_wrapped(wrap_pymodule!(generators))?;
Ok(())
}