#[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 DominatingSetError {
#[error("Invalid edge reference: edge ID {0} not found")]
InvalidEdgeReference(NodeId),
#[error("Invalid node reference: node ID {0} not found")]
InvalidNodeReference(NodeId),
}
#[cfg(feature = "hgraph")]
impl From<crate::core::TraversalError> for DominatingSetError {
fn from(error: crate::core::TraversalError) -> Self {
match error {
crate::core::TraversalError::InvalidNode(id) => {
DominatingSetError::InvalidNodeReference(id)
}
}
}
}
#[cfg(feature = "hgraph")]
pub type Result<T> = std::result::Result<T, DominatingSetError>;
#[cfg(feature = "hgraph")]
#[deprecated(
since = "3.0.0",
note = "superseded by `xgraph::core::DominatingSet`; `find_dominating_set_by_types` \
becomes `graph.of_edge_data(&[..]).dominating_set()`"
)]
pub trait HeteroDominatingSetFinder<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 + PartialEq,
{
fn find_dominating_set(&self) -> Result<HashSet<NodeId>>;
fn find_dominating_set_by_types(&self, allowed_edge_types: &[E]) -> Result<HashSet<NodeId>>;
}
#[cfg(feature = "hgraph")]
#[cfg(feature = "hgraph")]
#[allow(deprecated)]
impl<W, N, E> HeteroDominatingSetFinder<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 + PartialEq,
{
fn find_dominating_set(&self) -> Result<HashSet<NodeId>> {
self.find_dominating_set_by_types(&[])
}
fn find_dominating_set_by_types(&self, allowed_edge_types: &[E]) -> Result<HashSet<NodeId>> {
let by_data = self.of_edge_data(allowed_edge_types);
let connected: HashSet<NodeId> = crate::core::GraphView::node_ids(&by_data)
.filter(|&node| {
crate::core::GraphView::links(&by_data, node)
.next()
.is_some()
})
.collect();
let induced =
crate::core::NodeFiltered::new(&by_data, move |node| connected.contains(&node));
crate::core::DominatingSet::dominating_set(&induced).map_err(DominatingSetError::from)
}
}
#[cfg(test)]
#[allow(deprecated)]
#[cfg(feature = "hgraph")]
mod tests {
use super::*;
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, PartialEq)]
struct TestEdge(String);
impl EdgeType for TestEdge {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[test]
fn test_simple_dominating_set() {
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()));
let n3 = graph.add_node(TestNode("D".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("friend".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("friend".to_string()))
.unwrap();
graph
.add_edge(n1, n3, 1.0, TestEdge("friend".to_string()))
.unwrap();
let ds = graph.find_dominating_set().unwrap();
assert!(!ds.is_empty(), "Dominating set should not be empty");
assert!(
ds.len() <= 2,
"Dominating set size should be at most 2, got {}",
ds.len()
);
}
#[test]
fn test_triangle_dominating_set() {
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("friend".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("friend".to_string()))
.unwrap();
graph
.add_edge(n2, n0, 1.0, TestEdge("friend".to_string()))
.unwrap();
let ds = graph.find_dominating_set().unwrap();
assert_eq!(
ds.len(),
1,
"Dominating set for a triangle should be 1, got {}",
ds.len()
);
}
#[test]
fn test_disconnected_components() {
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()));
let n3 = graph.add_node(TestNode("D".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("friend".to_string()))
.unwrap();
graph
.add_edge(n2, n3, 1.0, TestEdge("friend".to_string()))
.unwrap();
let ds = graph.find_dominating_set().unwrap();
assert_eq!(
ds.len(),
2,
"Dominating set should contain 2 nodes for 2 components, got {}",
ds.len()
);
}
#[test]
fn test_multiple_edges() {
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("friend".to_string()))
.unwrap();
graph
.add_edge(n0, n1, 2.0, TestEdge("colleague".to_string()))
.unwrap();
let ds = graph.find_dominating_set().unwrap();
assert_eq!(
ds.len(),
1,
"Dominating set should be 1 despite multiple edges, got {}",
ds.len()
);
}
#[test]
fn test_dominating_set_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()));
let n3 = graph.add_node(TestNode("D".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("friend".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("colleague".to_string()))
.unwrap();
graph
.add_edge(n2, n3, 1.0, TestEdge("friend".to_string()))
.unwrap();
let allowed_edge_types = vec![TestEdge("friend".to_string())];
let ds = graph
.find_dominating_set_by_types(&allowed_edge_types)
.unwrap();
assert_eq!(
ds.len(),
2,
"Dominating set should contain 2 nodes when considering only 'friend' edges, got {}",
ds.len()
);
let covers_all =
(ds.contains(&n0) || ds.contains(&n1)) && (ds.contains(&n2) || ds.contains(&n3));
assert!(
covers_all,
"Dominating set should cover both components: {:?}",
ds
);
}
#[test]
fn test_dominating_set_by_types_isolated() {
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("friend".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("colleague".to_string()))
.unwrap();
let allowed_edge_types = vec![TestEdge("friend".to_string())];
let ds = graph
.find_dominating_set_by_types(&allowed_edge_types)
.unwrap();
assert_eq!(
ds.len(),
1,
"Dominating set should contain 1 node (n0 or n1) for connected 'friend' component, got {}",
ds.len()
);
assert!(
ds.contains(&n0) || ds.contains(&n1),
"Either n0 or n1 should be in the dominating set: {:?}",
ds
);
}
#[test]
fn test_invalid_edge_reference() {
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("friend".to_string()))
.unwrap();
let ds_result = graph.find_dominating_set();
assert!(ds_result.is_ok(), "Should succeed with valid edges");
}
}