#[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)]
pub enum DominatingSetError {
InvalidEdgeReference(usize),
}
#[cfg(feature = "hgraph")]
impl std::fmt::Display for DominatingSetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DominatingSetError::InvalidEdgeReference(id) => {
write!(f, "Invalid edge reference: edge ID {} not found", id)
}
}
}
}
#[cfg(feature = "hgraph")]
impl std::error::Error for DominatingSetError {}
#[cfg(feature = "hgraph")]
pub type Result<T> = std::result::Result<T, DominatingSetError>;
#[cfg(feature = "hgraph")]
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<usize>>;
fn find_dominating_set_by_types(&self, allowed_edge_types: &[E]) -> Result<HashSet<usize>>;
}
#[cfg(feature = "hgraph")]
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<usize>> {
self.find_dominating_set_by_types(&[])
}
fn find_dominating_set_by_types(&self, allowed_edge_types: &[E]) -> Result<HashSet<usize>> {
let mut dominating_set = HashSet::new();
let mut covered = HashSet::new();
let mut nodes_with_degree: Vec<(usize, usize)> = self
.nodes
.iter()
.map(|(id, _)| {
let degree = self
.get_neighbors(id)
.iter()
.filter(|(_, edges)| {
edges.iter().any(|(edge_id, _)| {
if let Some(edge) = self.edges.get(*edge_id) {
allowed_edge_types.is_empty()
|| allowed_edge_types.contains(&edge.data)
} else {
false
}
})
})
.count();
(id, degree)
})
.filter(|&(_, degree)| degree > 0) .collect();
nodes_with_degree.sort_by(|a, b| b.1.cmp(&a.1));
for (node, _) in &nodes_with_degree {
let neighbors: HashSet<usize> = self
.get_neighbors(*node)
.iter()
.filter(|(_, edges)| {
edges.iter().any(|(edge_id, _)| {
match self.edges.get(*edge_id) {
Some(edge) => {
allowed_edge_types.is_empty()
|| allowed_edge_types.contains(&edge.data)
}
None => false, }
})
})
.map(|(n, _)| *n)
.collect();
for &(edge_id, _) in self
.get_neighbors(*node)
.iter()
.flat_map(|(_, edges)| edges)
{
if self.edges.get(edge_id).is_none() {
return Err(DominatingSetError::InvalidEdgeReference(edge_id));
}
}
if !covered.contains(node) || neighbors.iter().any(|n| !covered.contains(n)) {
dominating_set.insert(*node);
covered.insert(*node);
for neighbor in neighbors {
covered.insert(neighbor);
}
}
}
Ok(dominating_set)
}
}
#[cfg(test)]
#[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");
}
}