use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TreeMode {
Out,
In,
Undirected,
}
impl TreeMode {
fn is_directed(self) -> bool {
!matches!(self, TreeMode::Undirected)
}
}
pub fn kary_tree(n: u32, children: u32, mode: TreeMode) -> IgraphResult<Graph> {
if children == 0 {
return Err(IgraphError::InvalidArgument(
"kary_tree: number of children must be positive".to_string(),
));
}
let directed = mode.is_directed();
if n == 0 {
return Graph::new(0, directed);
}
if n == 1 {
return Graph::new(1, directed);
}
let edge_count = (n as usize) - 1;
let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(edge_count);
let mut to: u32 = 1;
let mut parent: u32 = 0;
let mut emitted: u32 = 0;
let remaining = n - 1;
while emitted < remaining {
let batch = children.min(remaining - emitted);
for _ in 0..batch {
match mode {
TreeMode::Out | TreeMode::Undirected => edges.push((parent, to)),
TreeMode::In => edges.push((to, parent)),
}
to += 1;
emitted += 1;
}
parent += 1;
}
let mut g = Graph::new(n, directed)?;
g.add_edges(edges)?;
Ok(g)
}
#[cfg(test)]
mod tests {
use super::*;
fn collect_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
let m = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
(0..m)
.map(|eid| g.edge(eid).expect("edge id in bounds"))
.collect()
}
#[test]
fn empty_graph_all_modes() {
for mode in [TreeMode::Out, TreeMode::In, TreeMode::Undirected] {
let g = kary_tree(0, 2, mode).expect("tree n=0");
assert_eq!(g.vcount(), 0);
assert_eq!(g.ecount(), 0);
assert_eq!(g.is_directed(), mode.is_directed());
}
}
#[test]
fn singleton_has_no_edges() {
for mode in [TreeMode::Out, TreeMode::In, TreeMode::Undirected] {
let g = kary_tree(1, 3, mode).expect("tree n=1");
assert_eq!(g.vcount(), 1);
assert_eq!(g.ecount(), 0);
}
}
#[test]
fn binary_tree_seven_vertices_out_mode() {
let g = kary_tree(7, 2, TreeMode::Out).expect("B7");
assert!(g.is_directed());
assert_eq!(g.ecount(), 6);
assert_eq!(
collect_edges(&g),
vec![(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
);
}
#[test]
fn binary_tree_seven_vertices_in_mode() {
let g = kary_tree(7, 2, TreeMode::In).expect("B7 in");
assert!(g.is_directed());
assert_eq!(g.ecount(), 6);
assert_eq!(
collect_edges(&g),
vec![(1, 0), (2, 0), (3, 1), (4, 1), (5, 2), (6, 2)]
);
}
#[test]
fn binary_tree_seven_vertices_undirected_mode() {
let g = kary_tree(7, 2, TreeMode::Undirected).expect("B7 undir");
assert!(!g.is_directed());
assert_eq!(g.ecount(), 6);
assert_eq!(
collect_edges(&g),
vec![(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
);
}
#[test]
fn ternary_tree_eight_vertices_partial_last_parent() {
let g = kary_tree(8, 3, TreeMode::Out).expect("T8");
assert_eq!(g.ecount(), 7);
assert_eq!(
collect_edges(&g),
vec![(0, 1), (0, 2), (0, 3), (1, 4), (1, 5), (1, 6), (2, 7)]
);
}
#[test]
fn linear_chain_one_child_per_parent() {
let g = kary_tree(6, 1, TreeMode::Out).expect("path-6");
assert_eq!(g.ecount(), 5);
assert_eq!(
collect_edges(&g),
vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
);
}
#[test]
fn star_when_children_at_least_n_minus_one() {
let g = kary_tree(5, 10, TreeMode::Out).expect("star-5 via tree");
assert_eq!(g.ecount(), 4);
assert_eq!(collect_edges(&g), vec![(0, 1), (0, 2), (0, 3), (0, 4)]);
}
#[test]
fn zero_children_errors() {
let err = kary_tree(5, 0, TreeMode::Out).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn binary_tree_is_acyclic_and_connected() {
let g = kary_tree(15, 2, TreeMode::Undirected).expect("B15");
assert_eq!(g.ecount(), 14);
for v in 1..15u32 {
let degree = g.neighbors(v).expect("neighbors").len();
assert!(degree >= 1, "vertex {v} disconnected");
}
}
#[test]
fn child_count_pattern_for_n_seven_three() {
let g = kary_tree(7, 3, TreeMode::Out).expect("T7");
assert_eq!(g.ecount(), 6);
assert_eq!(
collect_edges(&g),
vec![(0, 1), (0, 2), (0, 3), (1, 4), (1, 5), (1, 6)]
);
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptest_tests {
use super::*;
use proptest::prelude::*;
fn arb_mode() -> impl Strategy<Value = TreeMode> {
prop_oneof![
Just(TreeMode::Out),
Just(TreeMode::In),
Just(TreeMode::Undirected),
]
}
proptest! {
#[test]
fn edge_count_is_n_minus_one(
n in 0u32..50,
children in 1u32..10,
mode in arb_mode(),
) {
let g = kary_tree(n, children, mode).unwrap();
prop_assert_eq!(g.vcount(), n);
prop_assert_eq!(g.is_directed(), mode.is_directed());
let expected = n.saturating_sub(1);
let m = u32::try_from(g.ecount()).unwrap();
prop_assert_eq!(m, expected);
}
#[test]
fn children_zero_always_errors(
n in 0u32..30,
mode in arb_mode(),
) {
prop_assert!(kary_tree(n, 0, mode).is_err());
}
#[test]
fn every_non_root_appears_exactly_once_as_child(
n in 2u32..30,
children in 1u32..6,
mode in arb_mode(),
) {
let g = kary_tree(n, children, mode).unwrap();
let m = u32::try_from(g.ecount()).unwrap();
let mut child_count = vec![0u32; n as usize];
for e in 0..m {
let (u, v) = g.edge(e).unwrap();
let child = match mode {
TreeMode::Out | TreeMode::Undirected => v,
TreeMode::In => u,
};
child_count[child as usize] += 1;
}
prop_assert_eq!(child_count[0], 0);
for v in 1..n {
prop_assert_eq!(child_count[v as usize], 1);
}
}
}
}