use std::cmp;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::ops::{Index, IndexMut};
use std::str;
use hashbrown::{HashMap, HashSet};
use pyo3::class::PyMappingProtocol;
use pyo3::exceptions::PyIndexError;
use pyo3::gc::{PyGCProtocol, PyVisit};
use pyo3::prelude::*;
use pyo3::types::{PyBool, PyDict, PyList, PyLong, PyString, PyTuple};
use pyo3::PyTraverseError;
use pyo3::Python;
use ndarray::prelude::*;
use numpy::PyReadonlyArray2;
use petgraph::algo;
use petgraph::graph::{EdgeIndex, NodeIndex};
use petgraph::prelude::*;
use petgraph::stable_graph::StableDiGraph;
use petgraph::stable_graph::StableUnGraph;
use petgraph::visit::{
GetAdjacencyMatrix, GraphBase, GraphProp, IntoEdgeReferences, IntoEdges,
IntoEdgesDirected, IntoNeighbors, IntoNeighborsDirected,
IntoNodeIdentifiers, IntoNodeReferences, NodeCompactIndexable, NodeCount,
NodeFiltered, NodeIndexable, Visitable,
};
use super::dot_utils::build_dot;
use super::iterators::{EdgeList, NodeIndices, WeightedEdgeList};
use super::{
is_directed_acyclic_graph, DAGHasCycle, DAGWouldCycle, NoEdgeBetweenNodes,
NoSuitableNeighbors, NodesRemoved,
};
#[pyclass(module = "retworkx", subclass, gc)]
#[text_signature = "(/, check_cycle=False, multigraph=True)"]
pub struct PyDiGraph {
pub graph: StableDiGraph<PyObject, PyObject>,
pub cycle_state: algo::DfsSpace<
NodeIndex,
<StableDiGraph<PyObject, PyObject> as Visitable>::Map,
>,
pub check_cycle: bool,
pub node_removed: bool,
pub multigraph: bool,
}
pub type Edges<'a, E> =
petgraph::stable_graph::Edges<'a, E, petgraph::Directed>;
impl GraphBase for PyDiGraph {
type NodeId = NodeIndex;
type EdgeId = EdgeIndex;
}
impl<'a> NodesRemoved for &'a PyDiGraph {
fn nodes_removed(&self) -> bool {
self.node_removed
}
}
impl NodeCount for PyDiGraph {
fn node_count(&self) -> usize {
self.graph.node_count()
}
}
impl GraphProp for PyDiGraph {
type EdgeType = petgraph::Directed;
fn is_directed(&self) -> bool {
true
}
}
impl petgraph::visit::Visitable for PyDiGraph {
type Map = <StableDiGraph<PyObject, PyObject> as Visitable>::Map;
fn visit_map(&self) -> Self::Map {
self.graph.visit_map()
}
fn reset_map(&self, map: &mut Self::Map) {
self.graph.reset_map(map)
}
}
impl petgraph::visit::Data for PyDiGraph {
type NodeWeight = PyObject;
type EdgeWeight = PyObject;
}
impl petgraph::data::DataMap for PyDiGraph {
fn node_weight(&self, id: Self::NodeId) -> Option<&Self::NodeWeight> {
self.graph.node_weight(id)
}
fn edge_weight(&self, id: Self::EdgeId) -> Option<&Self::EdgeWeight> {
self.graph.edge_weight(id)
}
}
impl petgraph::data::DataMapMut for PyDiGraph {
fn node_weight_mut(
&mut self,
id: Self::NodeId,
) -> Option<&mut Self::NodeWeight> {
self.graph.node_weight_mut(id)
}
fn edge_weight_mut(
&mut self,
id: Self::EdgeId,
) -> Option<&mut Self::EdgeWeight> {
self.graph.edge_weight_mut(id)
}
}
impl<'a> IntoNeighbors for &'a PyDiGraph {
type Neighbors = petgraph::stable_graph::Neighbors<'a, PyObject>;
fn neighbors(self, n: NodeIndex) -> Self::Neighbors {
self.graph.neighbors(n)
}
}
impl<'a> IntoNeighborsDirected for &'a PyDiGraph {
type NeighborsDirected = petgraph::stable_graph::Neighbors<'a, PyObject>;
fn neighbors_directed(
self,
n: NodeIndex,
d: petgraph::Direction,
) -> Self::Neighbors {
self.graph.neighbors_directed(n, d)
}
}
impl<'a> IntoEdgeReferences for &'a PyDiGraph {
type EdgeRef = petgraph::stable_graph::EdgeReference<'a, PyObject>;
type EdgeReferences = petgraph::stable_graph::EdgeReferences<'a, PyObject>;
fn edge_references(self) -> Self::EdgeReferences {
self.graph.edge_references()
}
}
impl<'a> IntoEdges for &'a PyDiGraph {
type Edges = Edges<'a, PyObject>;
fn edges(self, a: Self::NodeId) -> Self::Edges {
self.graph.edges(a)
}
}
impl<'a> IntoEdgesDirected for &'a PyDiGraph {
type EdgesDirected = Edges<'a, PyObject>;
fn edges_directed(
self,
a: Self::NodeId,
dir: petgraph::Direction,
) -> Self::EdgesDirected {
self.graph.edges_directed(a, dir)
}
}
impl<'a> IntoNodeIdentifiers for &'a PyDiGraph {
type NodeIdentifiers = petgraph::stable_graph::NodeIndices<'a, PyObject>;
fn node_identifiers(self) -> Self::NodeIdentifiers {
self.graph.node_identifiers()
}
}
impl<'a> IntoNodeReferences for &'a PyDiGraph {
type NodeRef = (NodeIndex, &'a PyObject);
type NodeReferences = petgraph::stable_graph::NodeReferences<'a, PyObject>;
fn node_references(self) -> Self::NodeReferences {
self.graph.node_references()
}
}
impl NodeIndexable for PyDiGraph {
fn node_bound(&self) -> usize {
self.graph.node_bound()
}
fn to_index(&self, ix: NodeIndex) -> usize {
self.graph.to_index(ix)
}
fn from_index(&self, ix: usize) -> Self::NodeId {
self.graph.from_index(ix)
}
}
impl NodeCompactIndexable for PyDiGraph {}
impl Index<NodeIndex> for PyDiGraph {
type Output = PyObject;
fn index(&self, index: NodeIndex) -> &PyObject {
&self.graph[index]
}
}
impl IndexMut<NodeIndex> for PyDiGraph {
fn index_mut(&mut self, index: NodeIndex) -> &mut PyObject {
&mut self.graph[index]
}
}
impl Index<EdgeIndex> for PyDiGraph {
type Output = PyObject;
fn index(&self, index: EdgeIndex) -> &PyObject {
&self.graph[index]
}
}
impl IndexMut<EdgeIndex> for PyDiGraph {
fn index_mut(&mut self, index: EdgeIndex) -> &mut PyObject {
&mut self.graph[index]
}
}
impl GetAdjacencyMatrix for PyDiGraph {
type AdjMatrix =
<StableDiGraph<PyObject, PyObject> as GetAdjacencyMatrix>::AdjMatrix;
fn adjacency_matrix(&self) -> Self::AdjMatrix {
self.graph.adjacency_matrix()
}
fn is_adjacent(
&self,
matrix: &Self::AdjMatrix,
a: NodeIndex,
b: NodeIndex,
) -> bool {
self.graph.is_adjacent(matrix, a, b)
}
}
impl PyDiGraph {
fn _add_edge(
&mut self,
p_index: NodeIndex,
c_index: NodeIndex,
edge: PyObject,
) -> PyResult<usize> {
if self.check_cycle {
let cycle_check_required =
is_cycle_check_required(self, p_index, c_index);
let state = Some(&mut self.cycle_state);
if cycle_check_required
&& algo::has_path_connecting(
&self.graph,
c_index,
p_index,
state,
)
{
return Err(DAGWouldCycle::new_err(
"Adding an edge would cycle",
));
}
}
if !self.multigraph {
let exists = self.graph.find_edge(p_index, c_index);
if let Some(index) = exists {
let edge_weight = self.graph.edge_weight_mut(index).unwrap();
*edge_weight = edge;
return Ok(index.index());
}
}
let edge = self.graph.add_edge(p_index, c_index, edge);
Ok(edge.index())
}
fn insert_between(
&mut self,
py: Python,
node: usize,
node_between: usize,
direction: bool,
) -> PyResult<()> {
let dir = if direction {
petgraph::Direction::Outgoing
} else {
petgraph::Direction::Incoming
};
let index = NodeIndex::new(node);
let node_between_index = NodeIndex::new(node_between);
let edges: Vec<(NodeIndex, EdgeIndex, PyObject)> = self
.graph
.edges_directed(node_between_index, dir)
.map(|edge| {
if direction {
(edge.target(), edge.id(), edge.weight().clone_ref(py))
} else {
(edge.source(), edge.id(), edge.weight().clone_ref(py))
}
})
.collect::<Vec<(NodeIndex, EdgeIndex, PyObject)>>();
for (other_index, edge_index, weight) in edges {
if direction {
self._add_edge(
node_between_index,
index,
weight.clone_ref(py),
)?;
self._add_edge(index, other_index, weight.clone_ref(py))?;
} else {
self._add_edge(other_index, index, weight.clone_ref(py))?;
self._add_edge(
index,
node_between_index,
weight.clone_ref(py),
)?;
}
self.graph.remove_edge(edge_index);
}
Ok(())
}
}
#[pymethods]
impl PyDiGraph {
#[new]
#[args(check_cycle = "false", multigraph = "true")]
fn new(check_cycle: bool, multigraph: bool) -> Self {
PyDiGraph {
graph: StableDiGraph::<PyObject, PyObject>::new(),
cycle_state: algo::DfsSpace::default(),
check_cycle,
node_removed: false,
multigraph,
}
}
fn __getstate__(&self, py: Python) -> PyResult<PyObject> {
let out_dict = PyDict::new(py);
let node_dict = PyDict::new(py);
let mut out_list: Vec<PyObject> =
Vec::with_capacity(self.graph.edge_count());
out_dict.set_item("nodes", node_dict)?;
out_dict.set_item("nodes_removed", self.node_removed)?;
out_dict.set_item("multigraph", self.multigraph)?;
let dir = petgraph::Direction::Incoming;
for node_index in self.graph.node_indices() {
let node_data = self.graph.node_weight(node_index).unwrap();
node_dict.set_item(node_index.index(), node_data)?;
for edge in self.graph.edges_directed(node_index, dir) {
let edge_w = edge.weight();
let triplet =
(edge.source().index(), edge.target().index(), edge_w)
.to_object(py);
out_list.push(triplet);
}
}
let py_out_list: PyObject = PyList::new(py, out_list).into();
out_dict.set_item("edges", py_out_list)?;
Ok(out_dict.into())
}
fn __setstate__(&mut self, py: Python, state: PyObject) -> PyResult<()> {
self.graph = StableDiGraph::<PyObject, PyObject>::new();
let dict_state = state.cast_as::<PyDict>(py)?;
let nodes_dict =
dict_state.get_item("nodes").unwrap().downcast::<PyDict>()?;
let edges_list =
dict_state.get_item("edges").unwrap().downcast::<PyList>()?;
let nodes_removed_raw = dict_state
.get_item("nodes_removed")
.unwrap()
.downcast::<PyBool>()?;
self.node_removed = nodes_removed_raw.extract()?;
let multigraph_raw = dict_state
.get_item("multigraph")
.unwrap()
.downcast::<PyBool>()?;
self.multigraph = multigraph_raw.extract()?;
let mut node_indices: Vec<usize> = Vec::new();
for raw_index in nodes_dict.keys() {
let tmp_index = raw_index.downcast::<PyLong>()?;
node_indices.push(tmp_index.extract()?);
}
if node_indices.is_empty() {
return Ok(());
}
let max_index: usize = *node_indices.iter().max().unwrap();
if max_index + 1 != node_indices.len() {
self.node_removed = true;
}
let mut tmp_nodes: Vec<NodeIndex> = Vec::new();
let mut node_count: usize = 0;
while max_index >= self.graph.node_bound() {
match nodes_dict.get_item(node_count) {
Some(raw_data) => {
self.graph.add_node(raw_data.into());
}
None => {
let tmp_node = self.graph.add_node(py.None());
tmp_nodes.push(tmp_node);
}
};
node_count += 1;
}
for tmp_node in tmp_nodes {
self.graph.remove_node(tmp_node);
}
for raw_edge in edges_list.iter() {
let edge = raw_edge.downcast::<PyTuple>()?;
let raw_p_index = edge.get_item(0).downcast::<PyLong>()?;
let p_index: usize = raw_p_index.extract()?;
let raw_c_index = edge.get_item(1).downcast::<PyLong>()?;
let c_index: usize = raw_c_index.extract()?;
let edge_data = edge.get_item(2);
self.graph.add_edge(
NodeIndex::new(p_index),
NodeIndex::new(c_index),
edge_data.into(),
);
}
Ok(())
}
#[getter]
fn get_check_cycle(&self) -> bool {
self.check_cycle
}
#[setter]
fn set_check_cycle(&mut self, value: bool) -> PyResult<()> {
if !self.check_cycle && value && !is_directed_acyclic_graph(self) {
return Err(DAGHasCycle::new_err("PyDiGraph object has a cycle"));
}
self.check_cycle = value;
Ok(())
}
#[getter]
fn multigraph(&self) -> bool {
self.multigraph
}
#[text_signature = "(self)"]
pub fn edges(&self) -> Vec<&PyObject> {
self.graph
.edge_indices()
.map(|edge| self.graph.edge_weight(edge).unwrap())
.collect()
}
#[text_signature = "(self)"]
pub fn nodes(&self) -> Vec<&PyObject> {
self.graph
.node_indices()
.map(|node| self.graph.node_weight(node).unwrap())
.collect()
}
#[text_signature = "(self)"]
pub fn node_indexes(&self) -> NodeIndices {
NodeIndices {
nodes: self.graph.node_indices().map(|node| node.index()).collect(),
}
}
#[text_signature = "(self, node_a, node_b, /)"]
pub fn has_edge(&self, node_a: usize, node_b: usize) -> bool {
let index_a = NodeIndex::new(node_a);
let index_b = NodeIndex::new(node_b);
self.graph.find_edge(index_a, index_b).is_some()
}
#[text_signature = "(self, node, /)"]
pub fn successors(&self, node: usize) -> Vec<&PyObject> {
let index = NodeIndex::new(node);
let children = self
.graph
.neighbors_directed(index, petgraph::Direction::Outgoing);
let mut succesors: Vec<&PyObject> = Vec::new();
let mut used_indexes: HashSet<NodeIndex> = HashSet::new();
for succ in children {
if !used_indexes.contains(&succ) {
succesors.push(self.graph.node_weight(succ).unwrap());
used_indexes.insert(succ);
}
}
succesors
}
#[text_signature = "(self, node, /)"]
pub fn predecessors(&self, node: usize) -> Vec<&PyObject> {
let index = NodeIndex::new(node);
let parents = self
.graph
.neighbors_directed(index, petgraph::Direction::Incoming);
let mut predec: Vec<&PyObject> = Vec::new();
let mut used_indexes: HashSet<NodeIndex> = HashSet::new();
for pred in parents {
if !used_indexes.contains(&pred) {
predec.push(self.graph.node_weight(pred).unwrap());
used_indexes.insert(pred);
}
}
predec
}
#[text_signature = "(self, node_a, node_b, /)"]
pub fn get_edge_data(
&self,
node_a: usize,
node_b: usize,
) -> PyResult<&PyObject> {
let index_a = NodeIndex::new(node_a);
let index_b = NodeIndex::new(node_b);
let edge_index = match self.graph.find_edge(index_a, index_b) {
Some(edge_index) => edge_index,
None => {
return Err(NoEdgeBetweenNodes::new_err(
"No edge found between nodes",
))
}
};
let data = self.graph.edge_weight(edge_index).unwrap();
Ok(data)
}
#[text_signature = "(self, source, target, edge /)"]
pub fn update_edge(
&mut self,
source: usize,
target: usize,
edge: PyObject,
) -> PyResult<()> {
let index_a = NodeIndex::new(source);
let index_b = NodeIndex::new(target);
let edge_index = match self.graph.find_edge(index_a, index_b) {
Some(edge_index) => edge_index,
None => {
return Err(NoEdgeBetweenNodes::new_err(
"No edge found between nodes",
))
}
};
let data = self.graph.edge_weight_mut(edge_index).unwrap();
*data = edge;
Ok(())
}
#[text_signature = "(self, source, target, edge /)"]
pub fn update_edge_by_index(
&mut self,
edge_index: usize,
edge: PyObject,
) -> PyResult<()> {
match self.graph.edge_weight_mut(EdgeIndex::new(edge_index)) {
Some(data) => *data = edge,
None => {
return Err(PyIndexError::new_err("No edge found for index"))
}
};
Ok(())
}
#[text_signature = "(self, node, /)"]
pub fn get_node_data(&self, node: usize) -> PyResult<&PyObject> {
let index = NodeIndex::new(node);
let node = match self.graph.node_weight(index) {
Some(node) => node,
None => {
return Err(PyIndexError::new_err("No node found for index"))
}
};
Ok(node)
}
#[text_signature = "(self, node_a, node_b, /)"]
pub fn get_all_edge_data(
&self,
node_a: usize,
node_b: usize,
) -> PyResult<Vec<&PyObject>> {
let index_a = NodeIndex::new(node_a);
let index_b = NodeIndex::new(node_b);
let raw_edges = self
.graph
.edges_directed(index_a, petgraph::Direction::Outgoing);
let out: Vec<&PyObject> = raw_edges
.filter(|x| x.target() == index_b)
.map(|edge| edge.weight())
.collect();
if out.is_empty() {
Err(NoEdgeBetweenNodes::new_err("No edge found between nodes"))
} else {
Ok(out)
}
}
pub fn edge_list(&self) -> EdgeList {
EdgeList {
edges: self
.edge_references()
.map(|edge| (edge.source().index(), edge.target().index()))
.collect(),
}
}
pub fn weighted_edge_list(&self, py: Python) -> WeightedEdgeList {
WeightedEdgeList {
edges: self
.edge_references()
.map(|edge| {
(
edge.source().index(),
edge.target().index(),
edge.weight().clone_ref(py),
)
})
.collect(),
}
}
#[text_signature = "(self, node, /)"]
pub fn remove_node(&mut self, node: usize) -> PyResult<()> {
let index = NodeIndex::new(node);
self.graph.remove_node(index);
self.node_removed = true;
Ok(())
}
#[text_signature = "(self, node, /, use_outgoing=None, condition=None)"]
#[args(use_outgoing = "false")]
pub fn remove_node_retain_edges(
&mut self,
py: Python,
node: usize,
use_outgoing: bool,
condition: Option<PyObject>,
) -> PyResult<()> {
let index = NodeIndex::new(node);
let mut edge_list: Vec<(NodeIndex, NodeIndex, PyObject)> = Vec::new();
fn check_condition(
py: Python,
condition: &Option<PyObject>,
in_weight: &PyObject,
out_weight: &PyObject,
) -> PyResult<bool> {
match condition {
Some(condition) => {
let res = condition.call1(py, (in_weight, out_weight))?;
Ok(res.extract(py)?)
}
None => Ok(true),
}
}
for (source, in_weight) in self
.graph
.edges_directed(index, petgraph::Direction::Incoming)
.map(|x| (x.source(), x.weight()))
{
for (target, out_weight) in self
.graph
.edges_directed(index, petgraph::Direction::Outgoing)
.map(|x| (x.target(), x.weight()))
{
let weight = if use_outgoing { out_weight } else { in_weight };
if check_condition(py, &condition, in_weight, out_weight)? {
edge_list.push((source, target, weight.clone_ref(py)));
}
}
}
for (source, target, weight) in edge_list {
self._add_edge(source, target, weight)?;
}
self.graph.remove_node(index);
self.node_removed = true;
Ok(())
}
#[text_signature = "(self, parent, child, edge, /)"]
pub fn add_edge(
&mut self,
parent: usize,
child: usize,
edge: PyObject,
) -> PyResult<usize> {
let p_index = NodeIndex::new(parent);
let c_index = NodeIndex::new(child);
let out_index = self._add_edge(p_index, c_index, edge)?;
Ok(out_index)
}
#[text_signature = "(self, obj_list, /)"]
pub fn add_edges_from(
&mut self,
obj_list: Vec<(usize, usize, PyObject)>,
) -> PyResult<Vec<usize>> {
let mut out_list: Vec<usize> = Vec::with_capacity(obj_list.len());
for obj in obj_list {
let p_index = NodeIndex::new(obj.0);
let c_index = NodeIndex::new(obj.1);
let edge = self._add_edge(p_index, c_index, obj.2)?;
out_list.push(edge);
}
Ok(out_list)
}
#[text_signature = "(self, obj_list, /)"]
pub fn add_edges_from_no_data(
&mut self,
py: Python,
obj_list: Vec<(usize, usize)>,
) -> PyResult<Vec<usize>> {
let mut out_list: Vec<usize> = Vec::with_capacity(obj_list.len());
for obj in obj_list {
let p_index = NodeIndex::new(obj.0);
let c_index = NodeIndex::new(obj.1);
let edge = self._add_edge(p_index, c_index, py.None())?;
out_list.push(edge);
}
Ok(out_list)
}
#[text_signature = "(self, edge_list, /)"]
pub fn extend_from_edge_list(
&mut self,
py: Python,
edge_list: Vec<(usize, usize)>,
) -> PyResult<()> {
for (source, target) in edge_list {
let max_index = cmp::max(source, target);
while max_index >= self.node_count() {
self.graph.add_node(py.None());
}
self._add_edge(
NodeIndex::new(source),
NodeIndex::new(target),
py.None(),
)?;
}
Ok(())
}
#[text_signature = "(self, edge_lsit, /)"]
pub fn extend_from_weighted_edge_list(
&mut self,
py: Python,
edge_list: Vec<(usize, usize, PyObject)>,
) -> PyResult<()> {
for (source, target, weight) in edge_list {
let max_index = cmp::max(source, target);
while max_index >= self.node_count() {
self.graph.add_node(py.None());
}
self._add_edge(
NodeIndex::new(source),
NodeIndex::new(target),
weight,
)?;
}
Ok(())
}
#[text_signature = "(self, node, ref_nodes, /)"]
pub fn insert_node_on_in_edges_multiple(
&mut self,
py: Python,
node: usize,
ref_nodes: Vec<usize>,
) -> PyResult<()> {
for ref_node in ref_nodes {
self.insert_between(py, node, ref_node, false)?;
}
Ok(())
}
#[text_signature = "(self, node, ref_nodes, /)"]
pub fn insert_node_on_out_edges_multiple(
&mut self,
py: Python,
node: usize,
ref_nodes: Vec<usize>,
) -> PyResult<()> {
for ref_node in ref_nodes {
self.insert_between(py, node, ref_node, true)?;
}
Ok(())
}
#[text_signature = "(self, node, ref_node, /)"]
pub fn insert_node_on_in_edges(
&mut self,
py: Python,
node: usize,
ref_node: usize,
) -> PyResult<()> {
self.insert_between(py, node, ref_node, false)?;
Ok(())
}
#[text_signature = "(self, node, ref_node, /)"]
pub fn insert_node_on_out_edges(
&mut self,
py: Python,
node: usize,
ref_node: usize,
) -> PyResult<()> {
self.insert_between(py, node, ref_node, true)?;
Ok(())
}
#[text_signature = "(self, parent, child, /)"]
pub fn remove_edge(&mut self, parent: usize, child: usize) -> PyResult<()> {
let p_index = NodeIndex::new(parent);
let c_index = NodeIndex::new(child);
let edge_index = match self.graph.find_edge(p_index, c_index) {
Some(edge_index) => edge_index,
None => {
return Err(NoEdgeBetweenNodes::new_err(
"No edge found between nodes",
))
}
};
self.graph.remove_edge(edge_index);
Ok(())
}
#[text_signature = "(self, edge, /)"]
pub fn remove_edge_from_index(&mut self, edge: usize) -> PyResult<()> {
let edge_index = EdgeIndex::new(edge);
self.graph.remove_edge(edge_index);
Ok(())
}
#[text_signature = "(self, index_list, /)"]
pub fn remove_edges_from(
&mut self,
index_list: Vec<(usize, usize)>,
) -> PyResult<()> {
for (p_index, c_index) in index_list
.iter()
.map(|(x, y)| (NodeIndex::new(*x), NodeIndex::new(*y)))
{
let edge_index = match self.graph.find_edge(p_index, c_index) {
Some(edge_index) => edge_index,
None => {
return Err(NoEdgeBetweenNodes::new_err(
"No edge found between nodes",
))
}
};
self.graph.remove_edge(edge_index);
}
Ok(())
}
#[text_signature = "(self, obj, /)"]
pub fn add_node(&mut self, obj: PyObject) -> PyResult<usize> {
let index = self.graph.add_node(obj);
Ok(index.index())
}
pub fn find_node_by_weight(
&self,
py: Python,
obj: PyObject,
) -> Option<usize> {
let mut index = None;
for node in self.graph.node_indices() {
let weight = self.graph.node_weight(node).unwrap();
let weight_compare = |a: &PyAny, b: &PyAny| -> PyResult<bool> {
let res = a.compare(b)?;
Ok(res == Ordering::Equal)
};
if weight_compare(obj.as_ref(py), weight.as_ref(py)).unwrap() {
index = Some(node.index());
break;
}
}
index
}
#[text_signature = "(self, u, v /)"]
pub fn merge_nodes(
&mut self,
py: Python,
u: usize,
v: usize,
) -> PyResult<()> {
let source_node = NodeIndex::new(u);
let target_node = NodeIndex::new(v);
let source_weight = match self.graph.node_weight(source_node) {
Some(weight) => weight,
None => {
return Err(PyIndexError::new_err("No node found for index"))
}
};
let target_weight = match self.graph.node_weight(target_node) {
Some(weight) => weight,
None => {
return Err(PyIndexError::new_err("No node found for index"))
}
};
let have_same_weights =
source_weight.as_ref(py).compare(target_weight.as_ref(py))?
== Ordering::Equal;
if have_same_weights {
const DIRECTIONS: [petgraph::Direction; 2] =
[petgraph::Direction::Outgoing, petgraph::Direction::Incoming];
let mut edges_to_add: Vec<(usize, usize, PyObject)> = Vec::new();
for dir in &DIRECTIONS {
for edge in self.graph.edges_directed(NodeIndex::new(u), *dir) {
let s = edge.source();
let d = edge.target();
if s.index() == u {
edges_to_add.push((
v,
d.index(),
edge.weight().clone_ref(py),
));
} else {
edges_to_add.push((
s.index(),
v,
edge.weight().clone_ref(py),
));
}
}
}
self.remove_node(u)?;
for edge in edges_to_add {
self.add_edge(edge.0, edge.1, edge.2)?;
}
}
Ok(())
}
#[text_signature = "(self, parent, obj, edge, /)"]
pub fn add_child(
&mut self,
parent: usize,
obj: PyObject,
edge: PyObject,
) -> PyResult<usize> {
let index = NodeIndex::new(parent);
let child_node = self.graph.add_node(obj);
self.graph.add_edge(index, child_node, edge);
Ok(child_node.index())
}
#[text_signature = "(self, child, obj, edge, /)"]
pub fn add_parent(
&mut self,
child: usize,
obj: PyObject,
edge: PyObject,
) -> PyResult<usize> {
let index = NodeIndex::new(child);
let parent_node = self.graph.add_node(obj);
self.graph.add_edge(parent_node, index, edge);
Ok(parent_node.index())
}
#[text_signature = "(self, node, /)"]
pub fn adj(&mut self, node: usize) -> HashMap<usize, &PyObject> {
let index = NodeIndex::new(node);
let neighbors = self.graph.neighbors(index);
let mut out_map: HashMap<usize, &PyObject> = HashMap::new();
for neighbor in neighbors {
let mut edge = self.graph.find_edge(index, neighbor);
if edge.is_none() {
edge = self.graph.find_edge(neighbor, index);
}
let edge_w = self.graph.edge_weight(edge.unwrap());
out_map.insert(neighbor.index(), edge_w.unwrap());
}
out_map
}
#[text_signature = "(self, node, direction, /)"]
pub fn adj_direction(
&mut self,
node: usize,
direction: bool,
) -> PyResult<HashMap<usize, &PyObject>> {
let index = NodeIndex::new(node);
let dir = if direction {
petgraph::Direction::Incoming
} else {
petgraph::Direction::Outgoing
};
let neighbors = self.graph.neighbors_directed(index, dir);
let mut out_map: HashMap<usize, &PyObject> = HashMap::new();
for neighbor in neighbors {
let edge = if direction {
match self.graph.find_edge(neighbor, index) {
Some(edge) => edge,
None => {
return Err(NoEdgeBetweenNodes::new_err(
"No edge found between nodes",
))
}
}
} else {
match self.graph.find_edge(index, neighbor) {
Some(edge) => edge,
None => {
return Err(NoEdgeBetweenNodes::new_err(
"No edge found between nodes",
))
}
}
};
let edge_w = self.graph.edge_weight(edge);
out_map.insert(neighbor.index(), edge_w.unwrap());
}
Ok(out_map)
}
#[text_signature = "(self, node, /)"]
pub fn neighbors(&self, node: usize) -> NodeIndices {
NodeIndices {
nodes: self
.graph
.neighbors(NodeIndex::new(node))
.map(|node| node.index())
.collect::<HashSet<usize>>()
.drain()
.collect(),
}
}
#[text_signature = "(self, node, /)"]
pub fn successor_indices(&mut self, node: usize) -> NodeIndices {
NodeIndices {
nodes: self
.graph
.neighbors_directed(
NodeIndex::new(node),
petgraph::Direction::Outgoing,
)
.map(|node| node.index())
.collect(),
}
}
#[text_signature = "(self, node, /)"]
pub fn predecessor_indices(&mut self, node: usize) -> NodeIndices {
NodeIndices {
nodes: self
.graph
.neighbors_directed(
NodeIndex::new(node),
petgraph::Direction::Incoming,
)
.map(|node| node.index())
.collect(),
}
}
#[text_signature = "(self, node, /)"]
pub fn in_edges(&self, py: Python, node: usize) -> WeightedEdgeList {
let index = NodeIndex::new(node);
let dir = petgraph::Direction::Incoming;
let raw_edges = self.graph.edges_directed(index, dir);
let out_list: Vec<(usize, usize, PyObject)> = raw_edges
.map(|x| (x.source().index(), node, x.weight().clone_ref(py)))
.collect();
WeightedEdgeList { edges: out_list }
}
#[text_signature = "(self, node, /)"]
pub fn out_edges(&self, py: Python, node: usize) -> WeightedEdgeList {
let index = NodeIndex::new(node);
let dir = petgraph::Direction::Outgoing;
let raw_edges = self.graph.edges_directed(index, dir);
let out_list: Vec<(usize, usize, PyObject)> = raw_edges
.map(|x| (node, x.target().index(), x.weight().clone_ref(py)))
.collect();
WeightedEdgeList { edges: out_list }
}
#[text_signature = "(self, obj_list, /)"]
pub fn add_nodes_from(&mut self, obj_list: Vec<PyObject>) -> NodeIndices {
let out_list: Vec<usize> = obj_list
.into_iter()
.map(|obj| self.graph.add_node(obj).index())
.collect();
NodeIndices { nodes: out_list }
}
#[text_signature = "(self, index_list, /)"]
pub fn remove_nodes_from(
&mut self,
index_list: Vec<usize>,
) -> PyResult<()> {
for node in index_list.iter().map(|x| NodeIndex::new(*x)) {
self.graph.remove_node(node);
}
Ok(())
}
#[text_signature = "(self, node, /)"]
pub fn in_degree(&self, node: usize) -> usize {
let index = NodeIndex::new(node);
let dir = petgraph::Direction::Incoming;
let neighbors = self.graph.edges_directed(index, dir);
neighbors.count()
}
#[text_signature = "(self, node, /)"]
pub fn out_degree(&self, node: usize) -> usize {
let index = NodeIndex::new(node);
let dir = petgraph::Direction::Outgoing;
let neighbors = self.graph.edges_directed(index, dir);
neighbors.count()
}
#[text_signature = "(self, node, predicate, /)"]
pub fn find_adjacent_node_by_edge(
&self,
py: Python,
node: usize,
predicate: PyObject,
) -> PyResult<&PyObject> {
let predicate_callable = |a: &PyObject| -> PyResult<PyObject> {
let res = predicate.call1(py, (a,))?;
Ok(res.to_object(py))
};
let index = NodeIndex::new(node);
let dir = petgraph::Direction::Outgoing;
let edges = self.graph.edges_directed(index, dir);
for edge in edges {
let edge_predicate_raw = predicate_callable(&edge.weight())?;
let edge_predicate: bool = edge_predicate_raw.extract(py)?;
if edge_predicate {
return Ok(self.graph.node_weight(edge.target()).unwrap());
}
}
Err(NoSuitableNeighbors::new_err("No suitable neighbor"))
}
#[text_signature = "(self, /, node_attr=None, edge_attr=None, graph_attr=None, filename=None)"]
pub fn to_dot(
&self,
py: Python,
node_attr: Option<PyObject>,
edge_attr: Option<PyObject>,
graph_attr: Option<BTreeMap<String, String>>,
filename: Option<String>,
) -> PyResult<Option<PyObject>> {
if filename.is_some() {
let mut file = File::create(filename.unwrap())?;
build_dot(py, self, &mut file, graph_attr, node_attr, edge_attr)?;
Ok(None)
} else {
let mut file = Vec::<u8>::new();
build_dot(py, self, &mut file, graph_attr, node_attr, edge_attr)?;
Ok(Some(
PyString::new(py, str::from_utf8(&file)?).to_object(py),
))
}
}
#[staticmethod]
#[text_signature = "(path, /, comment=None, deliminator=None)"]
pub fn read_edge_list(
py: Python,
path: &str,
comment: Option<String>,
deliminator: Option<String>,
) -> PyResult<PyDiGraph> {
let file = File::open(path)?;
let buf_reader = BufReader::new(file);
let mut out_graph = StableDiGraph::<PyObject, PyObject>::new();
for line_raw in buf_reader.lines() {
let line = line_raw?;
let skip = match &comment {
Some(comm) => line.trim().starts_with(comm),
None => line.trim().is_empty(),
};
if skip {
continue;
}
let line_no_comments = match &comment {
Some(comm) => line
.find(comm)
.map(|idx| &line[..idx])
.unwrap_or(&line)
.trim()
.to_string(),
None => line,
};
let pieces: Vec<&str> = match &deliminator {
Some(del) => line_no_comments.split(del).collect(),
None => line_no_comments.split_whitespace().collect(),
};
let src = pieces[0].parse::<usize>()?;
let target = pieces[1].parse::<usize>()?;
let max_index = cmp::max(src, target);
while max_index >= out_graph.node_count() {
out_graph.add_node(py.None());
}
let weight = if pieces.len() > 2 {
let weight_str = match &deliminator {
Some(del) => pieces[2..].join(del),
None => pieces[2..].join(&' '.to_string()),
};
PyString::new(py, &weight_str).into()
} else {
py.None()
};
out_graph.add_edge(
NodeIndex::new(src),
NodeIndex::new(target),
weight,
);
}
Ok(PyDiGraph {
graph: out_graph,
cycle_state: algo::DfsSpace::default(),
check_cycle: false,
node_removed: false,
multigraph: true,
})
}
#[staticmethod]
#[text_signature = "(matrix, /)"]
pub fn from_adjacency_matrix<'p>(
py: Python<'p>,
matrix: PyReadonlyArray2<'p, f64>,
) -> PyDiGraph {
let array = matrix.as_array();
let shape = array.shape();
let mut out_graph = StableDiGraph::<PyObject, PyObject>::new();
let _node_indices: Vec<NodeIndex> = (0..shape[0])
.map(|node| out_graph.add_node(node.to_object(py)))
.collect();
array
.axis_iter(Axis(0))
.enumerate()
.for_each(|(index, row)| {
let source_index = NodeIndex::new(index);
for target_index in 0..row.len() {
if row[[target_index]] > 0.0 {
out_graph.add_edge(
source_index,
NodeIndex::new(target_index),
row[[target_index]].to_object(py),
);
}
}
});
PyDiGraph {
graph: out_graph,
cycle_state: algo::DfsSpace::default(),
check_cycle: false,
node_removed: false,
multigraph: true,
}
}
#[text_signature = "(self, other, node_map, /, node_map_func=None, edge_map_func=None)"]
pub fn compose(
&mut self,
py: Python,
other: &PyDiGraph,
node_map: HashMap<usize, (usize, PyObject)>,
node_map_func: Option<PyObject>,
edge_map_func: Option<PyObject>,
) -> PyResult<PyObject> {
let mut new_node_map: HashMap<NodeIndex, NodeIndex> =
HashMap::with_capacity(other.node_count());
for node in other.graph.node_indices() {
let new_index = self.graph.add_node(weight_transform_callable(
py,
&node_map_func,
&other.graph[node],
)?);
new_node_map.insert(node, new_index);
}
for edge in other.graph.edge_references() {
let new_p_index = new_node_map.get(&edge.source()).unwrap();
let new_c_index = new_node_map.get(&edge.target()).unwrap();
let weight =
weight_transform_callable(py, &edge_map_func, edge.weight())?;
self._add_edge(*new_p_index, *new_c_index, weight)?;
}
for (this_index, (index, weight)) in node_map.iter() {
let new_index = new_node_map.get(&NodeIndex::new(*index)).unwrap();
self._add_edge(
NodeIndex::new(*this_index),
*new_index,
weight.clone_ref(py),
)?;
}
let out_dict = PyDict::new(py);
for (orig_node, new_node) in new_node_map.iter() {
out_dict.set_item(orig_node.index(), new_node.index())?;
}
Ok(out_dict.into())
}
#[text_signature = "(self, nodes, /)"]
pub fn subgraph(&self, py: Python, nodes: Vec<usize>) -> PyDiGraph {
let node_set: HashSet<usize> = nodes.iter().cloned().collect();
let mut node_map: HashMap<NodeIndex, NodeIndex> =
HashMap::with_capacity(nodes.len());
let node_filter =
|node: NodeIndex| -> bool { node_set.contains(&node.index()) };
let mut out_graph = StableDiGraph::<PyObject, PyObject>::new();
let filtered = NodeFiltered(self, node_filter);
for node in filtered.node_references() {
let new_node = out_graph.add_node(node.1.clone_ref(py));
node_map.insert(node.0, new_node);
}
for edge in filtered.edge_references() {
let new_source = *node_map.get(&edge.source()).unwrap();
let new_target = *node_map.get(&edge.target()).unwrap();
out_graph.add_edge(
new_source,
new_target,
edge.weight().clone_ref(py),
);
}
PyDiGraph {
graph: out_graph,
node_removed: false,
cycle_state: algo::DfsSpace::default(),
check_cycle: self.check_cycle,
multigraph: self.multigraph,
}
}
#[text_signature = "(self)"]
pub fn is_symmetric(&self) -> bool {
let mut edges: HashSet<(NodeIndex, NodeIndex)> = HashSet::new();
for (source, target) in self
.graph
.edge_references()
.map(|edge| (edge.source(), edge.target()))
{
let edge = (source, target);
let reversed = (target, source);
if edges.contains(&reversed) {
edges.remove(&reversed);
} else {
edges.insert(edge);
}
}
edges.is_empty()
}
#[text_signature = "(self)"]
pub fn to_undirected(&self, py: Python) -> crate::graph::PyGraph {
let mut new_graph = StableUnGraph::<PyObject, PyObject>::default();
let mut node_map: HashMap<NodeIndex, NodeIndex> =
HashMap::with_capacity(self.node_count());
for node_index in self.graph.node_indices() {
let node = self.graph[node_index].clone_ref(py);
let new_index = new_graph.add_node(node);
node_map.insert(node_index, new_index);
}
for edge in self.edge_references() {
let source = node_map.get(&edge.source()).unwrap();
let target = node_map.get(&edge.target()).unwrap();
let weight = edge.weight().clone_ref(py);
new_graph.add_edge(*source, *target, weight);
}
crate::graph::PyGraph {
graph: new_graph,
node_removed: false,
multigraph: true,
}
}
}
#[pyproto]
impl PyMappingProtocol for PyDiGraph {
fn __len__(&self) -> PyResult<usize> {
Ok(self.graph.node_count())
}
fn __getitem__(&'p self, idx: usize) -> PyResult<&'p PyObject> {
match self.graph.node_weight(NodeIndex::new(idx as usize)) {
Some(data) => Ok(data),
None => Err(PyIndexError::new_err("No node found for index")),
}
}
fn __setitem__(&'p mut self, idx: usize, value: PyObject) -> PyResult<()> {
let data = match self
.graph
.node_weight_mut(NodeIndex::new(idx as usize))
{
Some(node_data) => node_data,
None => {
return Err(PyIndexError::new_err("No node found for index"))
}
};
*data = value;
Ok(())
}
fn __delitem__(&'p mut self, idx: usize) -> PyResult<()> {
match self.graph.remove_node(NodeIndex::new(idx as usize)) {
Some(_) => Ok(()),
None => Err(PyIndexError::new_err("No node found for index")),
}
}
}
fn is_cycle_check_required(
dag: &PyDiGraph,
a: NodeIndex,
b: NodeIndex,
) -> bool {
let mut parents_a = dag
.graph
.neighbors_directed(a, petgraph::Direction::Incoming);
let mut children_b = dag
.graph
.neighbors_directed(b, petgraph::Direction::Outgoing);
parents_a.next().is_some()
&& children_b.next().is_some()
&& dag.graph.find_edge(a, b).is_none()
}
fn weight_transform_callable(
py: Python,
map_fn: &Option<PyObject>,
value: &PyObject,
) -> PyResult<PyObject> {
match map_fn {
Some(map_fn) => {
let res = map_fn.call1(py, (value,))?;
Ok(res.to_object(py))
}
None => Ok(value.clone_ref(py)),
}
}
#[pyproto]
impl PyGCProtocol for PyDiGraph {
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
for node in self
.graph
.node_indices()
.map(|node| self.graph.node_weight(node).unwrap())
{
visit.call(node)?;
}
for edge in self
.graph
.edge_indices()
.map(|edge| self.graph.edge_weight(edge).unwrap())
{
visit.call(edge)?;
}
Ok(())
}
fn __clear__(&mut self) {
self.graph = StableDiGraph::<PyObject, PyObject>::new();
self.node_removed = false;
}
}