#[cfg(feature = "hgraph")]
use crate::core::view::NodeId;
#[cfg(feature = "hgraph")]
use crate::hgraph::h_graph::HeterogeneousGraph;
#[cfg(feature = "hgraph")]
use std::collections::HashSet;
#[cfg(feature = "hgraph")]
use std::fmt::Debug;
#[cfg(feature = "hgraph")]
use std::hash::Hash;
#[cfg(feature = "hgraph")]
#[derive(Debug, thiserror::Error)]
pub enum SearchError {
#[error("Invalid node reference: node ID {0} not found")]
InvalidNodeReference(NodeId),
}
#[cfg(feature = "hgraph")]
impl From<crate::core::TraversalError> for SearchError {
fn from(error: crate::core::TraversalError) -> Self {
match error {
crate::core::TraversalError::InvalidNode(id) => SearchError::InvalidNodeReference(id),
}
}
}
#[cfg(feature = "hgraph")]
pub type Result<T> = std::result::Result<T, SearchError>;
#[cfg(feature = "hgraph")]
#[deprecated(
since = "3.0.0",
note = "superseded by `xgraph::core::Traversal`; the `*_by_types` variants become \
`graph.of_edge_kinds(&[..])` followed by the ordinary method"
)]
pub trait HeteroSearch<W, N, E>
where
W: Copy + Default + PartialEq + Debug,
N: Clone + Eq + Hash + Debug + crate::hgraph::h_node::NodeType,
E: Clone + Default + Debug + crate::hgraph::h_edge::EdgeType,
{
fn has_path(&self, start: NodeId, target: NodeId) -> bool;
fn has_path_by_types(&self, start: NodeId, target: NodeId, edge_types: &[&str]) -> bool;
fn bfs_path(&self, start: NodeId, target: NodeId) -> Result<Option<Vec<NodeId>>>;
fn bfs_path_by_types(
&self,
start: NodeId,
target: NodeId,
edge_types: &[&str],
) -> Result<Option<Vec<NodeId>>>;
fn dfs(&self, current: NodeId, target: NodeId, visited: &mut HashSet<NodeId>) -> bool;
fn dfs_by_types(
&self,
current: NodeId,
target: NodeId,
visited: &mut HashSet<NodeId>,
edge_types: &[&str],
) -> bool;
fn has_node(&self, node: NodeId) -> bool;
fn has_cycle(&self) -> bool;
fn has_cycle_by_types(&self, edge_types: &[&str]) -> bool;
fn has_cycle_directed(
&self,
node: NodeId,
visited: &mut HashSet<NodeId>,
recursion_stack: &mut HashSet<NodeId>,
) -> bool;
fn has_cycle_directed_by_types(
&self,
node: NodeId,
visited: &mut HashSet<NodeId>,
recursion_stack: &mut HashSet<NodeId>,
edge_types: &[&str],
) -> bool;
fn has_cycle_undirected(
&self,
node: NodeId,
parent: Option<NodeId>,
visited: &mut HashSet<NodeId>,
) -> bool;
fn has_cycle_undirected_by_types(
&self,
node: NodeId,
parent: Option<NodeId>,
visited: &mut HashSet<NodeId>,
edge_types: &[&str],
) -> bool;
}
#[cfg(feature = "hgraph")]
#[cfg(feature = "hgraph")]
#[allow(deprecated)]
impl<W, N, E> HeteroSearch<W, N, E> for HeterogeneousGraph<W, N, E>
where
W: Copy + Default + PartialEq + Debug,
N: Clone + Eq + Hash + Debug + crate::hgraph::h_node::NodeType,
E: Clone + Default + Debug + crate::hgraph::h_edge::EdgeType,
{
fn has_path(&self, start: NodeId, target: NodeId) -> bool {
crate::core::Traversal::has_path(self, start, target).unwrap_or(false)
}
fn has_path_by_types(&self, start: NodeId, target: NodeId, edge_types: &[&str]) -> bool {
crate::core::Traversal::has_path(&self.of_edge_kinds(edge_types), start, target)
.unwrap_or(false)
}
fn bfs_path(&self, start: NodeId, target: NodeId) -> Result<Option<Vec<NodeId>>> {
crate::core::Traversal::bfs_path(self, start, target).map_err(SearchError::from)
}
fn bfs_path_by_types(
&self,
start: NodeId,
target: NodeId,
edge_types: &[&str],
) -> Result<Option<Vec<NodeId>>> {
crate::core::Traversal::bfs_path(&self.of_edge_kinds(edge_types), start, target)
.map_err(SearchError::from)
}
fn dfs(&self, current: NodeId, target: NodeId, visited: &mut HashSet<NodeId>) -> bool {
crate::core::Traversal::explore_into(self, current, Some(target), visited).unwrap_or(false)
}
fn dfs_by_types(
&self,
current: NodeId,
target: NodeId,
visited: &mut HashSet<NodeId>,
edge_types: &[&str],
) -> bool {
crate::core::Traversal::explore_into(
&self.of_edge_kinds(edge_types),
current,
Some(target),
visited,
)
.unwrap_or(false)
}
fn has_node(&self, node: NodeId) -> bool {
crate::core::GraphView::contains_node(self, node)
}
fn has_cycle(&self) -> bool {
crate::core::Traversal::has_cycle(self).unwrap_or(false)
}
fn has_cycle_by_types(&self, edge_types: &[&str]) -> bool {
crate::core::Traversal::has_cycle(&self.of_edge_kinds(edge_types)).unwrap_or(false)
}
fn has_cycle_directed(
&self,
node: NodeId,
visited: &mut HashSet<NodeId>,
_recursion_stack: &mut HashSet<NodeId>,
) -> bool {
crate::core::Traversal::has_cycle_from(self, node, visited).unwrap_or(false)
}
fn has_cycle_directed_by_types(
&self,
node: NodeId,
visited: &mut HashSet<NodeId>,
_recursion_stack: &mut HashSet<NodeId>,
edge_types: &[&str],
) -> bool {
crate::core::Traversal::has_cycle_from(&self.of_edge_kinds(edge_types), node, visited)
.unwrap_or(false)
}
fn has_cycle_undirected(
&self,
node: NodeId,
_parent: Option<NodeId>,
visited: &mut HashSet<NodeId>,
) -> bool {
crate::core::Traversal::has_cycle_from(self, node, visited).unwrap_or(false)
}
fn has_cycle_undirected_by_types(
&self,
node: NodeId,
_parent: Option<NodeId>,
visited: &mut HashSet<NodeId>,
edge_types: &[&str],
) -> bool {
crate::core::Traversal::has_cycle_from(&self.of_edge_kinds(edge_types), node, visited)
.unwrap_or(false)
}
}
#[cfg(test)]
#[cfg(feature = "hgraph")]
#[allow(deprecated)]
mod tests {
use super::*;
use crate::core::view::NodeId;
use crate::hgraph::h_edge::EdgeType;
use crate::hgraph::h_node::NodeType;
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct TestNode(String);
impl NodeType for TestNode {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[derive(Clone, Debug, Default)]
struct TestEdge(String);
impl EdgeType for TestEdge {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[test]
fn test_has_path() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
assert!(graph.has_path(n0, n1));
assert!(graph.has_path(n1, n0));
}
#[test]
fn test_has_path_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
assert!(graph.has_path_by_types(n0, n1, &["road"]));
assert!(!graph.has_path_by_types(n0, n2, &["road"]));
assert!(graph.has_path_by_types(n0, n2, &["road", "bridge"]));
}
#[test]
fn test_bfs_path() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
assert_eq!(graph.bfs_path(n0, n2).unwrap(), Some(vec![n0, n1, n2]));
}
#[test]
fn test_bfs_path_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
assert_eq!(
graph.bfs_path_by_types(n0, n1, &["road"]).unwrap(),
Some(vec![n0, n1])
);
assert_eq!(graph.bfs_path_by_types(n0, n2, &["road"]).unwrap(), None);
assert_eq!(
graph
.bfs_path_by_types(n0, n2, &["road", "bridge"])
.unwrap(),
Some(vec![n0, n1, n2])
);
}
#[test]
fn test_dfs() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
let mut visited = HashSet::new();
assert!(graph.dfs(n0, n2, &mut visited));
}
#[test]
fn test_invalid_nodes() {
let graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
assert!(!graph.has_node(NodeId::from_index(0)));
assert!(!graph.has_path(NodeId::from_index(0), NodeId::from_index(1)));
assert!(!graph.has_path_by_types(NodeId::from_index(0), NodeId::from_index(1), &["road"]));
assert!(matches!(
graph.bfs_path(NodeId::from_index(0), NodeId::from_index(1)),
Err(SearchError::InvalidNodeReference(_))
));
assert!(matches!(
graph.bfs_path_by_types(NodeId::from_index(0), NodeId::from_index(1), &["road"]),
Err(SearchError::InvalidNodeReference(_))
));
}
#[test]
fn test_cycle_detection_directed() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n2, n0, 1.0, TestEdge("path".to_string()))
.unwrap();
assert!(graph.has_cycle());
}
#[test]
fn test_cycle_detection_directed_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n2, n0, 1.0, TestEdge("path".to_string()))
.unwrap();
assert!(graph.has_cycle_by_types(&["road", "bridge", "path"]));
assert!(!graph.has_cycle_by_types(&["road", "bridge"]));
}
#[test]
fn test_cycle_detection_undirected() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n2, n0, 1.0, TestEdge("path".to_string()))
.unwrap();
assert!(graph.has_cycle());
}
#[test]
fn test_cycle_detection_undirected_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n2, n0, 1.0, TestEdge("path".to_string()))
.unwrap();
assert!(graph.has_cycle_by_types(&["road", "bridge", "path"]));
assert!(!graph.has_cycle_by_types(&["road", "bridge"]));
}
#[test]
fn test_no_cycle_directed() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
assert!(!graph.has_cycle());
}
#[test]
fn test_no_cycle_directed_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
assert!(!graph.has_cycle_by_types(&["road", "bridge"]));
}
#[test]
fn test_no_cycle_undirected() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
assert!(!graph.has_cycle());
}
#[test]
fn test_no_cycle_undirected_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
assert!(!graph.has_cycle_by_types(&["road", "bridge"]));
}
#[test]
fn test_multiple_edges() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n0, n1, 2.0, TestEdge("path".to_string()))
.unwrap();
assert!(graph.has_path(n0, n1));
assert!(graph.has_path_by_types(n0, n1, &["road"]));
assert_eq!(graph.bfs_path(n0, n1).unwrap(), Some(vec![n0, n1]));
assert_eq!(
graph.bfs_path_by_types(n0, n1, &["path"]).unwrap(),
Some(vec![n0, n1])
);
assert!(!graph.has_cycle());
assert!(!graph.has_cycle_by_types(&["road", "path"]));
}
}