use std::collections::VecDeque;
use crate::core::Graph;
use crate::core::cache::CachedProperty;
use crate::core::graph::VertexId;
#[must_use]
pub fn is_dag(graph: &Graph) -> bool {
if !graph.is_directed() {
return false;
}
if let Some(v) = graph.cache_get(CachedProperty::IsDag) {
return v;
}
let n = graph.vcount();
let n_us = n as usize;
let mut in_deg: Vec<u32> = Vec::with_capacity(n_us);
for v in 0..n {
let deg = u32::try_from(
graph
.incident_in(v)
.expect("incident_in on valid vertex")
.len(),
)
.unwrap_or(u32::MAX);
in_deg.push(deg);
}
let mut sources: VecDeque<VertexId> = VecDeque::new();
for v in 0..n {
if in_deg[v as usize] == 0 {
sources.push_back(v);
}
}
let mut peeled: u32 = 0;
while let Some(v) = sources.pop_front() {
peeled = peeled.saturating_add(1);
let out_eids = graph.incident(v).expect("incident on valid vertex");
for eid in out_eids {
let nei = graph.edge_other(eid, v).expect("edge_other on valid edge");
if nei == v {
return false;
}
let nei_idx = nei as usize;
in_deg[nei_idx] = in_deg[nei_idx].saturating_sub(1);
if in_deg[nei_idx] == 0 {
sources.push_back(nei);
}
}
}
let result = peeled == n;
graph.cache_set(CachedProperty::IsDag, result);
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Graph;
#[test]
fn empty_directed_graph_is_dag() {
let g = Graph::new(0, true).unwrap();
assert!(is_dag(&g));
}
#[test]
fn isolated_vertices_only_is_dag() {
let g = Graph::new(5, true).unwrap();
assert!(is_dag(&g));
}
#[test]
fn linear_chain_is_dag() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
assert!(is_dag(&g));
}
#[test]
fn binary_tree_is_dag() {
let mut g = Graph::new(5, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3), (1, 4)])
.unwrap();
assert!(is_dag(&g));
}
#[test]
fn three_cycle_is_not_dag() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
assert!(!is_dag(&g));
}
#[test]
fn self_loop_is_not_dag() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
assert!(!is_dag(&g));
}
#[test]
fn undirected_graph_is_not_dag_even_if_acyclic() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
assert!(!is_dag(&g));
}
#[test]
fn dag_with_multiple_roots() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 2u32), (1, 2)]).unwrap();
assert!(is_dag(&g));
}
#[test]
fn dag_with_parallel_edges_still_dag() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert!(is_dag(&g));
}
#[test]
fn cycle_with_extra_tail_branches_not_dag() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0), (3, 0)])
.unwrap();
assert!(!is_dag(&g));
}
#[test]
fn back_edge_to_root_is_not_dag() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
assert!(!is_dag(&g));
}
#[test]
fn two_disjoint_dags_is_dag() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (2, 3)]).unwrap();
assert!(is_dag(&g));
}
}