use super::kary_tree::TreeMode;
use super::symmetric_tree::symmetric_tree;
use crate::core::{Graph, IgraphError, IgraphResult};
pub fn regular_tree(h: u32, k: u32, mode: TreeMode) -> IgraphResult<Graph> {
if h < 1 {
return Err(IgraphError::InvalidArgument(format!(
"regular_tree: height must be at least 1, got {h}"
)));
}
if k < 2 {
return Err(IgraphError::InvalidArgument(format!(
"regular_tree: degree must be at least 2, got {k}"
)));
}
let mut branches: Vec<u32> = vec![k - 1; h as usize];
branches[0] = k;
symmetric_tree(&branches, mode)
}
#[cfg(test)]
mod tests {
use super::*;
fn dump_edges(g: &Graph) -> Vec<(u32, u32)> {
let m = u32::try_from(g.ecount()).expect("ecount fits in u32 in tests");
(0..m)
.map(|e| g.edge(e).expect("edge id in bounds in tests"))
.collect()
}
#[test]
fn h1_k3_is_star_k1_3() {
let t = regular_tree(1, 3, TreeMode::Out).expect("star-like");
assert_eq!(t.vcount(), 4);
assert_eq!(t.ecount(), 3);
assert_eq!(dump_edges(&t), vec![(0, 1), (0, 2), (0, 3)]);
}
#[test]
fn h2_k3_root_has_three_kids_each_has_two() {
let t = regular_tree(2, 3, TreeMode::Out).expect("h2 k3");
assert_eq!(t.vcount(), 10);
assert_eq!(t.ecount(), 9);
assert_eq!(
dump_edges(&t),
vec![
(0, 1),
(0, 2),
(0, 3),
(1, 4),
(1, 5),
(2, 6),
(2, 7),
(3, 8),
(3, 9),
]
);
}
#[test]
fn h3_k2_is_linear_chain() {
let t = regular_tree(3, 2, TreeMode::Out).expect("h3 k2");
assert_eq!(t.vcount(), 1 + 2 + 2 + 2);
assert_eq!(t.ecount(), 6);
}
#[test]
fn h1_k2_is_single_edge_pair() {
let t = regular_tree(1, 2, TreeMode::Undirected).expect("h1 k2");
assert_eq!(t.vcount(), 3);
assert_eq!(t.ecount(), 2);
}
#[test]
fn in_mode_reverses_arcs() {
let t = regular_tree(1, 3, TreeMode::In).expect("in h1 k3");
assert_eq!(dump_edges(&t), vec![(1, 0), (2, 0), (3, 0)]);
}
#[test]
fn undirected_is_undirected() {
let t = regular_tree(2, 3, TreeMode::Undirected).expect("undirected");
assert!(!t.is_directed());
}
#[test]
fn h_zero_rejected() {
let err = regular_tree(0, 3, TreeMode::Out).expect_err("h=0");
match err {
IgraphError::InvalidArgument(msg) => assert!(msg.contains("height")),
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn k_less_than_two_rejected() {
let err = regular_tree(3, 1, TreeMode::Out).expect_err("k=1");
match err {
IgraphError::InvalidArgument(msg) => assert!(msg.contains("degree")),
other => panic!("unexpected error: {other:?}"),
}
let err0 = regular_tree(3, 0, TreeMode::Out).expect_err("k=0");
match err0 {
IgraphError::InvalidArgument(msg) => assert!(msg.contains("degree")),
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn vertex_count_overflow_propagates() {
let err = regular_tree(40, 3, TreeMode::Out).expect_err("overflow");
match err {
IgraphError::InvalidArgument(msg) => assert!(msg.contains("overflow")),
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn root_has_degree_k() {
let t = regular_tree(3, 4, TreeMode::Out).expect("h3 k4");
let mut root_out_degree = 0u32;
let m = u32::try_from(t.ecount()).unwrap();
for e in 0..m {
let (u, _v) = t.edge(e).unwrap();
if u == 0 {
root_out_degree += 1;
}
}
assert_eq!(root_out_degree, 4);
}
#[test]
fn internal_non_root_vertex_has_total_degree_k_undirected() {
let t = regular_tree(3, 4, TreeMode::Undirected).expect("h3 k4");
let m = u32::try_from(t.ecount()).unwrap();
let mut deg1 = 0u32;
for e in 0..m {
let (u, v) = t.edge(e).unwrap();
if u == 1 || v == 1 {
deg1 += 1;
}
}
assert_eq!(deg1, 4);
}
#[test]
fn h2_k4_vertex_and_edge_counts() {
let t = regular_tree(2, 4, TreeMode::Out).expect("h2 k4");
assert_eq!(t.vcount(), 17);
assert_eq!(t.ecount(), 16);
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn edge_count_is_n_minus_one(h in 1u32..=4, k in 2u32..=4) {
let t = regular_tree(h, k, TreeMode::Out).unwrap();
let n = t.vcount();
prop_assert_eq!(t.ecount(), (n as usize) - 1);
}
#[test]
fn out_and_in_endpoints_swap(h in 1u32..=3, k in 2u32..=3) {
let out = regular_tree(h, k, TreeMode::Out).unwrap();
let inn = regular_tree(h, k, TreeMode::In).unwrap();
prop_assert_eq!(out.vcount(), inn.vcount());
prop_assert_eq!(out.ecount(), inn.ecount());
let m = u32::try_from(out.ecount()).unwrap();
for e in 0..m {
let (a, b) = out.edge(e).unwrap();
let (c, d) = inn.edge(e).unwrap();
prop_assert_eq!((a, b), (d, c));
}
}
#[test]
fn root_out_degree_equals_k(h in 1u32..=3, k in 2u32..=4) {
let t = regular_tree(h, k, TreeMode::Out).unwrap();
let m = u32::try_from(t.ecount()).unwrap();
let mut root_deg = 0u32;
for e in 0..m {
let (u, _v) = t.edge(e).unwrap();
if u == 0 { root_deg += 1; }
}
prop_assert_eq!(root_deg, k);
}
}
}