use crate::base::{DiGraph, EdgeWeight, Graph, IndexType, Node};
use crate::error::{GraphError, Result};
#[cfg(test)]
use serde::Deserialize;
use serde::{de::DeserializeOwned, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, PartialEq)]
pub enum AttributeValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Json(serde_json::Value),
}
impl AttributeValue {
pub fn string<S: Into<String>>(value: S) -> Self {
AttributeValue::String(value.into())
}
pub fn integer(value: i64) -> Self {
AttributeValue::Integer(value)
}
pub fn float(value: f64) -> Self {
AttributeValue::Float(value)
}
pub fn boolean(value: bool) -> Self {
AttributeValue::Boolean(value)
}
pub fn json<T: Serialize>(value: &T) -> Result<Self> {
let json_value =
serde_json::to_value(value).map_err(|_| GraphError::SerializationError {
format: "JSON".to_string(),
details: "Failed to serialize to JSON".to_string(),
})?;
Ok(AttributeValue::Json(json_value))
}
pub fn as_string(&self) -> Option<&str> {
match self {
AttributeValue::String(s) => Some(s),
_ => None,
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
AttributeValue::Integer(i) => Some(*i),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
AttributeValue::Float(f) => Some(*f),
_ => None,
}
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
AttributeValue::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {
match self {
AttributeValue::Json(json) => {
serde_json::from_value(json.clone()).map_err(|_| GraphError::SerializationError {
format: "JSON".to_string(),
details: "Failed to deserialize from JSON".to_string(),
})
}
_ => Err(GraphError::InvalidAttribute {
attribute: "value".to_string(),
target_type: "JSON".to_string(),
details: "Attribute is not JSON".to_string(),
}),
}
}
pub fn to_string_repr(&self) -> String {
match self {
AttributeValue::String(s) => s.clone(),
AttributeValue::Integer(i) => i.to_string(),
AttributeValue::Float(f) => f.to_string(),
AttributeValue::Boolean(b) => b.to_string(),
AttributeValue::Json(json) => json.to_string(),
}
}
}
impl Eq for AttributeValue {}
impl Hash for AttributeValue {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
AttributeValue::String(s) => {
0u8.hash(state);
s.hash(state);
}
AttributeValue::Integer(i) => {
1u8.hash(state);
i.hash(state);
}
AttributeValue::Float(f) => {
2u8.hash(state);
f.to_bits().hash(state);
}
AttributeValue::Boolean(b) => {
3u8.hash(state);
b.hash(state);
}
AttributeValue::Json(json) => {
4u8.hash(state);
json.to_string().hash(state);
}
}
}
}
pub type Attributes = HashMap<String, AttributeValue>;
pub struct AttributedGraph<N: Node, E: EdgeWeight, Ix: IndexType = u32> {
graph: Graph<N, E, Ix>,
node_attributes: HashMap<N, Attributes>,
edge_attributes: HashMap<(N, N), Attributes>,
graph_attributes: Attributes,
}
pub struct AttributedDiGraph<N: Node, E: EdgeWeight, Ix: IndexType = u32> {
graph: DiGraph<N, E, Ix>,
node_attributes: HashMap<N, Attributes>,
edge_attributes: HashMap<(N, N), Attributes>,
graph_attributes: Attributes,
}
impl<N: Node + std::fmt::Debug + std::fmt::Display, E: EdgeWeight, Ix: IndexType> Default
for AttributedGraph<N, E, Ix>
{
fn default() -> Self {
Self::new()
}
}
impl<N: Node + std::fmt::Debug + std::fmt::Display, E: EdgeWeight, Ix: IndexType>
AttributedGraph<N, E, Ix>
{
pub fn new() -> Self {
AttributedGraph {
graph: Graph::new(),
node_attributes: HashMap::new(),
edge_attributes: HashMap::new(),
graph_attributes: HashMap::new(),
}
}
pub fn from_graph(graph: Graph<N, E, Ix>) -> Self {
AttributedGraph {
graph,
node_attributes: HashMap::new(),
edge_attributes: HashMap::new(),
graph_attributes: HashMap::new(),
}
}
pub fn add_node(&mut self, node: N) {
self.graph.add_node(node.clone());
self.node_attributes.entry(node).or_default();
}
pub fn add_node_with_attributes(&mut self, node: N, attributes: Attributes) {
self.graph.add_node(node.clone());
self.node_attributes.insert(node, attributes);
}
pub fn add_edge(&mut self, source: N, target: N, weight: E) -> Result<()> {
self.graph
.add_edge(source.clone(), target.clone(), weight)?;
self.edge_attributes.entry((source, target)).or_default();
Ok(())
}
pub fn add_edge_with_attributes(
&mut self,
source: N,
target: N,
weight: E,
attributes: Attributes,
) -> Result<()> {
self.graph
.add_edge(source.clone(), target.clone(), weight)?;
self.edge_attributes.insert((source, target), attributes);
Ok(())
}
pub fn set_node_attribute<K: Into<String>>(&mut self, node: &N, key: K, value: AttributeValue) {
self.node_attributes
.entry(node.clone())
.or_default()
.insert(key.into(), value);
}
pub fn get_node_attribute(&self, node: &N, key: &str) -> Option<&AttributeValue> {
self.node_attributes.get(node)?.get(key)
}
pub fn get_node_attributes(&self, node: &N) -> Option<&Attributes> {
self.node_attributes.get(node)
}
pub fn get_node_attributes_mut(&mut self, node: &N) -> Option<&mut Attributes> {
self.node_attributes.get_mut(node)
}
pub fn remove_node_attribute(&mut self, node: &N, key: &str) -> Option<AttributeValue> {
self.node_attributes.get_mut(node)?.remove(key)
}
pub fn set_edge_attribute<K: Into<String>>(
&mut self,
source: &N,
target: &N,
key: K,
value: AttributeValue,
) -> Result<()> {
if !self.graph.has_edge(source, target) {
return Err(GraphError::edge_not_found(source, target));
}
self.edge_attributes
.entry((source.clone(), target.clone()))
.or_default()
.insert(key.into(), value);
Ok(())
}
pub fn get_edge_attribute(&self, source: &N, target: &N, key: &str) -> Option<&AttributeValue> {
self.edge_attributes
.get(&(source.clone(), target.clone()))?
.get(key)
}
pub fn get_edge_attributes(&self, source: &N, target: &N) -> Option<&Attributes> {
self.edge_attributes.get(&(source.clone(), target.clone()))
}
pub fn get_edge_attributes_mut(&mut self, source: &N, target: &N) -> Option<&mut Attributes> {
self.edge_attributes
.get_mut(&(source.clone(), target.clone()))
}
pub fn remove_edge_attribute(
&mut self,
source: &N,
target: &N,
key: &str,
) -> Option<AttributeValue> {
self.edge_attributes
.get_mut(&(source.clone(), target.clone()))?
.remove(key)
}
pub fn set_graph_attribute<K: Into<String>>(&mut self, key: K, value: AttributeValue) {
self.graph_attributes.insert(key.into(), value);
}
pub fn get_graph_attribute(&self, key: &str) -> Option<&AttributeValue> {
self.graph_attributes.get(key)
}
pub fn get_graph_attributes(&self) -> &Attributes {
&self.graph_attributes
}
pub fn remove_graph_attribute(&mut self, key: &str) -> Option<AttributeValue> {
self.graph_attributes.remove(key)
}
pub fn nodes_with_attribute(&self, key: &str) -> Vec<&N> {
self.node_attributes
.iter()
.filter_map(|(node, attrs)| {
if attrs.contains_key(key) {
Some(node)
} else {
None
}
})
.collect()
}
pub fn nodes_with_attribute_value(&self, key: &str, value: &AttributeValue) -> Vec<&N> {
self.node_attributes
.iter()
.filter_map(|(node, attrs)| {
if let Some(attr_value) = attrs.get(key) {
if matches_attribute_value(attr_value, value) {
Some(node)
} else {
None
}
} else {
None
}
})
.collect()
}
pub fn edges_with_attribute(&self, key: &str) -> Vec<(&N, &N)> {
self.edge_attributes
.iter()
.filter_map(|((source, target), attrs)| {
if attrs.contains_key(key) {
Some((source, target))
} else {
None
}
})
.collect()
}
pub fn edges_with_attribute_value(&self, key: &str, value: &AttributeValue) -> Vec<(&N, &N)> {
self.edge_attributes
.iter()
.filter_map(|((source, target), attrs)| {
if let Some(attr_value) = attrs.get(key) {
if matches_attribute_value(attr_value, value) {
Some((source, target))
} else {
None
}
} else {
None
}
})
.collect()
}
pub fn graph(&self) -> &Graph<N, E, Ix> {
&self.graph
}
pub fn graph_mut(&mut self) -> &mut Graph<N, E, Ix> {
&mut self.graph
}
pub fn into_graph(self) -> Graph<N, E, Ix> {
self.graph
}
pub fn filter_nodes_by_attribute(&self, key: &str, value: &AttributeValue) -> Self
where
N: Clone,
E: Clone,
{
let matching_nodes: std::collections::HashSet<N> = self
.nodes_with_attribute_value(key, value)
.into_iter()
.cloned()
.collect();
let mut new_graph = AttributedGraph::new();
for node in &matching_nodes {
if let Some(attrs) = self.get_node_attributes(node) {
new_graph.add_node_with_attributes(node.clone(), attrs.clone());
} else {
new_graph.add_node(node.clone());
}
}
for edge in self.graph.edges() {
if matching_nodes.contains(&edge.source) && matching_nodes.contains(&edge.target) {
if let Some(attrs) = self.get_edge_attributes(&edge.source, &edge.target) {
new_graph
.add_edge_with_attributes(
edge.source.clone(),
edge.target.clone(),
edge.weight.clone(),
attrs.clone(),
)
.expect("Operation failed");
} else {
new_graph
.add_edge(
edge.source.clone(),
edge.target.clone(),
edge.weight.clone(),
)
.expect("Operation failed");
}
}
}
new_graph.graph_attributes = self.graph_attributes.clone();
new_graph
}
pub fn attribute_summary(&self) -> AttributeSummary {
let mut node_attribute_keys = std::collections::HashSet::new();
let mut edge_attribute_keys = std::collections::HashSet::new();
for attrs in self.node_attributes.values() {
for key in attrs.keys() {
node_attribute_keys.insert(key.clone());
}
}
for attrs in self.edge_attributes.values() {
for key in attrs.keys() {
edge_attribute_keys.insert(key.clone());
}
}
AttributeSummary {
nodes_with_attributes: self.node_attributes.len(),
edges_with_attributes: self.edge_attributes.len(),
unique_node_attribute_keys: node_attribute_keys.len(),
unique_edge_attribute_keys: edge_attribute_keys.len(),
graph_attribute_keys: self.graph_attributes.len(),
node_attribute_keys: node_attribute_keys.into_iter().collect(),
edge_attribute_keys: edge_attribute_keys.into_iter().collect(),
}
}
}
impl<N: Node + std::fmt::Debug + std::fmt::Display, E: EdgeWeight, Ix: IndexType> Default
for AttributedDiGraph<N, E, Ix>
{
fn default() -> Self {
Self::new()
}
}
impl<N: Node + std::fmt::Debug + std::fmt::Display, E: EdgeWeight, Ix: IndexType>
AttributedDiGraph<N, E, Ix>
{
pub fn new() -> Self {
AttributedDiGraph {
graph: DiGraph::new(),
node_attributes: HashMap::new(),
edge_attributes: HashMap::new(),
graph_attributes: HashMap::new(),
}
}
pub fn from_digraph(graph: DiGraph<N, E, Ix>) -> Self {
AttributedDiGraph {
graph,
node_attributes: HashMap::new(),
edge_attributes: HashMap::new(),
graph_attributes: HashMap::new(),
}
}
pub fn add_node(&mut self, node: N) {
self.graph.add_node(node.clone());
self.node_attributes.entry(node).or_default();
}
pub fn add_node_with_attributes(&mut self, node: N, attributes: Attributes) {
self.graph.add_node(node.clone());
self.node_attributes.insert(node, attributes);
}
pub fn add_edge(&mut self, source: N, target: N, weight: E) -> Result<()> {
self.graph
.add_edge(source.clone(), target.clone(), weight)?;
self.edge_attributes.entry((source, target)).or_default();
Ok(())
}
pub fn add_edge_with_attributes(
&mut self,
source: N,
target: N,
weight: E,
attributes: Attributes,
) -> Result<()> {
self.graph
.add_edge(source.clone(), target.clone(), weight)?;
self.edge_attributes.insert((source, target), attributes);
Ok(())
}
pub fn set_node_attribute<K: Into<String>>(&mut self, node: &N, key: K, value: AttributeValue) {
self.node_attributes
.entry(node.clone())
.or_default()
.insert(key.into(), value);
}
pub fn get_node_attribute(&self, node: &N, key: &str) -> Option<&AttributeValue> {
self.node_attributes.get(node)?.get(key)
}
pub fn set_edge_attribute<K: Into<String>>(
&mut self,
source: &N,
target: &N,
key: K,
value: AttributeValue,
) -> Result<()> {
if !self.graph.has_edge(source, target) {
return Err(GraphError::edge_not_found(source, target));
}
self.edge_attributes
.entry((source.clone(), target.clone()))
.or_default()
.insert(key.into(), value);
Ok(())
}
pub fn get_edge_attribute(&self, source: &N, target: &N, key: &str) -> Option<&AttributeValue> {
self.edge_attributes
.get(&(source.clone(), target.clone()))?
.get(key)
}
pub fn set_graph_attribute<K: Into<String>>(&mut self, key: K, value: AttributeValue) {
self.graph_attributes.insert(key.into(), value);
}
pub fn get_graph_attribute(&self, key: &str) -> Option<&AttributeValue> {
self.graph_attributes.get(key)
}
pub fn predecessors(&self, node: &N) -> Result<Vec<N>>
where
N: Clone,
{
self.graph.predecessors(node)
}
pub fn successors(&self, node: &N) -> Result<Vec<N>>
where
N: Clone,
{
self.graph.successors(node)
}
pub fn graph(&self) -> &DiGraph<N, E, Ix> {
&self.graph
}
pub fn graph_mut(&mut self) -> &mut DiGraph<N, E, Ix> {
&mut self.graph
}
pub fn into_digraph(self) -> DiGraph<N, E, Ix> {
self.graph
}
}
#[derive(Debug, Clone)]
pub struct AttributeSummary {
pub nodes_with_attributes: usize,
pub edges_with_attributes: usize,
pub unique_node_attribute_keys: usize,
pub unique_edge_attribute_keys: usize,
pub graph_attribute_keys: usize,
pub node_attribute_keys: Vec<String>,
pub edge_attribute_keys: Vec<String>,
}
#[allow(dead_code)]
fn matches_attribute_value(_attr_value: &AttributeValue, targetvalue: &AttributeValue) -> bool {
match (_attr_value, targetvalue) {
(AttributeValue::String(a), AttributeValue::String(b)) => a == b,
(AttributeValue::Integer(a), AttributeValue::Integer(b)) => a == b,
(AttributeValue::Float(a), AttributeValue::Float(b)) => (a - b).abs() < f64::EPSILON,
(AttributeValue::Boolean(a), AttributeValue::Boolean(b)) => a == b,
(AttributeValue::Json(a), AttributeValue::Json(b)) => a == b,
(AttributeValue::Integer(a), AttributeValue::Float(b)) => {
(*a as f64 - b).abs() < f64::EPSILON
}
(AttributeValue::Float(a), AttributeValue::Integer(b)) => {
(a - *b as f64).abs() < f64::EPSILON
}
_ => false,
}
}
pub struct AttributeView<'a, N: Node> {
attributes: &'a HashMap<N, Attributes>,
}
impl<'a, N: Node> AttributeView<'a, N> {
pub fn new(attributes: &'a HashMap<N, Attributes>) -> Self {
AttributeView { attributes }
}
pub fn nodes_in_numeric_range(&self, key: &str, min: f64, max: f64) -> Vec<&N> {
self.attributes
.iter()
.filter_map(|(node, attrs)| {
if let Some(attr_value) = attrs.get(key) {
let numeric_value = match attr_value {
AttributeValue::Integer(i) => Some(*i as f64),
AttributeValue::Float(f) => Some(*f),
_ => None,
};
if let Some(value) = numeric_value {
if value >= min && value <= max {
Some(node)
} else {
None
}
} else {
None
}
} else {
None
}
})
.collect()
}
pub fn nodes_matching_pattern(&self, key: &str, pattern: &str) -> Vec<&N> {
self.attributes
.iter()
.filter_map(|(node, attrs)| {
if let Some(AttributeValue::String(s)) = attrs.get(key) {
if s.contains(pattern) {
Some(node)
} else {
None
}
} else {
None
}
})
.collect()
}
pub fn unique_values(&self, key: &str) -> Vec<&AttributeValue> {
let mut values = std::collections::HashSet::new();
for attrs in self.attributes.values() {
if let Some(value) = attrs.get(key) {
values.insert(value);
}
}
values.into_iter().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attribute_value_creation() {
let str_attr = AttributeValue::string("test");
assert_eq!(str_attr.as_string(), Some("test"));
let int_attr = AttributeValue::integer(42);
assert_eq!(int_attr.as_integer(), Some(42));
let float_attr = AttributeValue::float(3.15);
assert_eq!(float_attr.as_float(), Some(3.15));
let bool_attr = AttributeValue::boolean(true);
assert_eq!(bool_attr.as_boolean(), Some(true));
}
#[test]
fn test_attribute_value_json() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestData {
name: String,
value: i32,
}
let data = TestData {
name: "test".to_string(),
value: 123,
};
let json_attr = AttributeValue::json(&data).expect("Operation failed");
let recovered: TestData = json_attr.as_json().expect("Operation failed");
assert_eq!(recovered, data);
}
#[test]
fn test_attributed_graph_basic_operations() {
let mut graph: AttributedGraph<&str, f64> = AttributedGraph::new();
let mut node_attrs = HashMap::new();
node_attrs.insert("type".to_string(), AttributeValue::string("person"));
node_attrs.insert("age".to_string(), AttributeValue::integer(30));
graph.add_node_with_attributes("Alice", node_attrs);
graph.add_node("Bob");
graph.set_node_attribute(&"Bob", "type", AttributeValue::string("person"));
graph.set_node_attribute(&"Bob", "age", AttributeValue::integer(25));
graph
.add_edge("Alice", "Bob", 1.0)
.expect("Operation failed");
graph
.set_edge_attribute(
&"Alice",
&"Bob",
"relationship",
AttributeValue::string("friend"),
)
.expect("Operation failed");
assert_eq!(
graph
.get_node_attribute(&"Alice", "type")
.expect("Operation failed")
.as_string(),
Some("person")
);
assert_eq!(
graph
.get_node_attribute(&"Alice", "age")
.expect("Operation failed")
.as_integer(),
Some(30)
);
assert_eq!(
graph
.get_edge_attribute(&"Alice", &"Bob", "relationship")
.expect("Operation failed")
.as_string(),
Some("friend")
);
}
#[test]
fn test_attributed_graph_filtering() {
let mut graph: AttributedGraph<i32, f64> = AttributedGraph::new();
graph.add_node(1);
graph.set_node_attribute(&1, "type", AttributeValue::string("server"));
graph.add_node(2);
graph.set_node_attribute(&2, "type", AttributeValue::string("client"));
graph.add_node(3);
graph.set_node_attribute(&3, "type", AttributeValue::string("server"));
graph.add_edge(1, 2, 1.0).expect("Operation failed");
graph.add_edge(1, 3, 2.0).expect("Operation failed");
let servers = graph.nodes_with_attribute_value("type", &AttributeValue::string("server"));
assert_eq!(servers.len(), 2);
assert!(servers.contains(&&1));
assert!(servers.contains(&&3));
let server_subgraph =
graph.filter_nodes_by_attribute("type", &AttributeValue::string("server"));
assert_eq!(server_subgraph.graph().node_count(), 2);
assert_eq!(server_subgraph.graph().edge_count(), 1); }
#[test]
fn test_attribute_summary() {
let mut graph: AttributedGraph<&str, f64> = AttributedGraph::new();
graph.add_node("A");
graph.set_node_attribute(&"A", "category", AttributeValue::string("important"));
graph.add_node("B");
graph.set_node_attribute(&"B", "category", AttributeValue::string("normal"));
graph.set_node_attribute(&"B", "weight", AttributeValue::float(1.5));
graph.add_edge("A", "B", 1.0).expect("Operation failed");
graph
.set_edge_attribute(&"A", &"B", "type", AttributeValue::string("connection"))
.expect("Operation failed");
graph.set_graph_attribute("name", AttributeValue::string("test_graph"));
let summary = graph.attribute_summary();
assert_eq!(summary.nodes_with_attributes, 2);
assert_eq!(summary.edges_with_attributes, 1);
assert_eq!(summary.unique_node_attribute_keys, 2); assert_eq!(summary.unique_edge_attribute_keys, 1); assert_eq!(summary.graph_attribute_keys, 1); }
#[test]
fn test_attribute_view() {
let mut attributes = HashMap::new();
let mut attrs1 = HashMap::new();
attrs1.insert("score".to_string(), AttributeValue::float(85.5));
attrs1.insert("name".to_string(), AttributeValue::string("Alice"));
attributes.insert("person1", attrs1);
let mut attrs2 = HashMap::new();
attrs2.insert("score".to_string(), AttributeValue::float(92.0));
attrs2.insert("name".to_string(), AttributeValue::string("Bob"));
attributes.insert("person2", attrs2);
let mut attrs3 = HashMap::new();
attrs3.insert("score".to_string(), AttributeValue::integer(88));
attrs3.insert("name".to_string(), AttributeValue::string("Charlie"));
attributes.insert("person3", attrs3);
let view = AttributeView::new(&attributes);
let high_scorers = view.nodes_in_numeric_range("score", 90.0, 100.0);
assert_eq!(high_scorers.len(), 1);
assert!(high_scorers.contains(&&"person2"));
let names_with_a = view.nodes_matching_pattern("name", "a");
assert_eq!(names_with_a.len(), 1); assert!(names_with_a.contains(&&"person3"));
let names_with_capital_a = view.nodes_matching_pattern("name", "A");
assert_eq!(names_with_capital_a.len(), 1); assert!(names_with_capital_a.contains(&&"person1"));
let unique_scores = view.unique_values("score");
assert_eq!(unique_scores.len(), 3);
}
#[test]
fn test_attribute_value_matching() {
let int_val = AttributeValue::integer(42);
let float_val = AttributeValue::float(42.0);
let string_val = AttributeValue::string("42");
assert!(matches_attribute_value(
&int_val,
&AttributeValue::integer(42)
));
assert!(matches_attribute_value(&int_val, &float_val)); assert!(!matches_attribute_value(&int_val, &string_val));
assert!(matches_attribute_value(
&float_val,
&AttributeValue::float(42.0)
));
assert!(matches_attribute_value(&float_val, &int_val));
assert!(matches_attribute_value(
&string_val,
&AttributeValue::string("42")
));
}
#[test]
fn test_graph_level_attributes() {
let mut graph: AttributedGraph<i32, f64> = AttributedGraph::new();
graph.set_graph_attribute("title", AttributeValue::string("Test Network"));
graph.set_graph_attribute("created", AttributeValue::string("2024"));
graph.set_graph_attribute("version", AttributeValue::float(1.0));
assert_eq!(
graph
.get_graph_attribute("title")
.expect("Operation failed")
.as_string(),
Some("Test Network")
);
assert_eq!(
graph
.get_graph_attribute("version")
.expect("Operation failed")
.as_float(),
Some(1.0)
);
assert_eq!(graph.get_graph_attributes().len(), 3);
let removed = graph.remove_graph_attribute("created");
assert!(removed.is_some());
assert_eq!(graph.get_graph_attributes().len(), 2);
}
#[test]
fn test_attributed_digraph() {
let mut digraph: AttributedDiGraph<&str, f64> = AttributedDiGraph::new();
digraph.add_node("A");
digraph.add_node("B");
digraph.add_edge("A", "B", 1.0).expect("Operation failed");
assert_eq!(digraph.graph().node_count(), 2);
assert_eq!(digraph.graph().edge_count(), 1);
assert!(digraph.graph().has_edge(&"A", &"B"));
assert!(!digraph.graph().has_edge(&"B", &"A")); }
}