rust_igraph/algorithms/constructors/
regular_tree.rs1use super::kary_tree::TreeMode;
36use super::symmetric_tree::symmetric_tree;
37use crate::core::{Graph, IgraphError, IgraphResult};
38
39pub fn regular_tree(h: u32, k: u32, mode: TreeMode) -> IgraphResult<Graph> {
68 if h < 1 {
69 return Err(IgraphError::InvalidArgument(format!(
70 "regular_tree: height must be at least 1, got {h}"
71 )));
72 }
73 if k < 2 {
74 return Err(IgraphError::InvalidArgument(format!(
75 "regular_tree: degree must be at least 2, got {k}"
76 )));
77 }
78
79 let mut branches: Vec<u32> = vec![k - 1; h as usize];
84 branches[0] = k;
85
86 symmetric_tree(&branches, mode)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn dump_edges(g: &Graph) -> Vec<(u32, u32)> {
94 let m = u32::try_from(g.ecount()).expect("ecount fits in u32 in tests");
95 (0..m)
96 .map(|e| g.edge(e).expect("edge id in bounds in tests"))
97 .collect()
98 }
99
100 #[test]
101 fn h1_k3_is_star_k1_3() {
102 let t = regular_tree(1, 3, TreeMode::Out).expect("star-like");
104 assert_eq!(t.vcount(), 4);
105 assert_eq!(t.ecount(), 3);
106 assert_eq!(dump_edges(&t), vec![(0, 1), (0, 2), (0, 3)]);
107 }
108
109 #[test]
110 fn h2_k3_root_has_three_kids_each_has_two() {
111 let t = regular_tree(2, 3, TreeMode::Out).expect("h2 k3");
114 assert_eq!(t.vcount(), 10);
115 assert_eq!(t.ecount(), 9);
116 assert_eq!(
118 dump_edges(&t),
119 vec![
120 (0, 1),
121 (0, 2),
122 (0, 3),
123 (1, 4),
124 (1, 5),
125 (2, 6),
126 (2, 7),
127 (3, 8),
128 (3, 9),
129 ]
130 );
131 }
132
133 #[test]
134 fn h3_k2_is_linear_chain() {
135 let t = regular_tree(3, 2, TreeMode::Out).expect("h3 k2");
137 assert_eq!(t.vcount(), 1 + 2 + 2 + 2);
138 assert_eq!(t.ecount(), 6);
139 }
140
141 #[test]
142 fn h1_k2_is_single_edge_pair() {
143 let t = regular_tree(1, 2, TreeMode::Undirected).expect("h1 k2");
146 assert_eq!(t.vcount(), 3);
147 assert_eq!(t.ecount(), 2);
148 }
149
150 #[test]
151 fn in_mode_reverses_arcs() {
152 let t = regular_tree(1, 3, TreeMode::In).expect("in h1 k3");
153 assert_eq!(dump_edges(&t), vec![(1, 0), (2, 0), (3, 0)]);
154 }
155
156 #[test]
157 fn undirected_is_undirected() {
158 let t = regular_tree(2, 3, TreeMode::Undirected).expect("undirected");
159 assert!(!t.is_directed());
160 }
161
162 #[test]
163 fn h_zero_rejected() {
164 let err = regular_tree(0, 3, TreeMode::Out).expect_err("h=0");
165 match err {
166 IgraphError::InvalidArgument(msg) => assert!(msg.contains("height")),
167 other => panic!("unexpected error: {other:?}"),
168 }
169 }
170
171 #[test]
172 fn k_less_than_two_rejected() {
173 let err = regular_tree(3, 1, TreeMode::Out).expect_err("k=1");
174 match err {
175 IgraphError::InvalidArgument(msg) => assert!(msg.contains("degree")),
176 other => panic!("unexpected error: {other:?}"),
177 }
178 let err0 = regular_tree(3, 0, TreeMode::Out).expect_err("k=0");
179 match err0 {
180 IgraphError::InvalidArgument(msg) => assert!(msg.contains("degree")),
181 other => panic!("unexpected error: {other:?}"),
182 }
183 }
184
185 #[test]
186 fn vertex_count_overflow_propagates() {
187 let err = regular_tree(40, 3, TreeMode::Out).expect_err("overflow");
190 match err {
191 IgraphError::InvalidArgument(msg) => assert!(msg.contains("overflow")),
192 other => panic!("unexpected error: {other:?}"),
193 }
194 }
195
196 #[test]
197 fn root_has_degree_k() {
198 let t = regular_tree(3, 4, TreeMode::Out).expect("h3 k4");
200 let mut root_out_degree = 0u32;
201 let m = u32::try_from(t.ecount()).unwrap();
202 for e in 0..m {
203 let (u, _v) = t.edge(e).unwrap();
204 if u == 0 {
205 root_out_degree += 1;
206 }
207 }
208 assert_eq!(root_out_degree, 4);
209 }
210
211 #[test]
212 fn internal_non_root_vertex_has_total_degree_k_undirected() {
213 let t = regular_tree(3, 4, TreeMode::Undirected).expect("h3 k4");
216 let m = u32::try_from(t.ecount()).unwrap();
217 let mut deg1 = 0u32;
220 for e in 0..m {
221 let (u, v) = t.edge(e).unwrap();
222 if u == 1 || v == 1 {
223 deg1 += 1;
224 }
225 }
226 assert_eq!(deg1, 4);
227 }
228
229 #[test]
230 fn h2_k4_vertex_and_edge_counts() {
231 let t = regular_tree(2, 4, TreeMode::Out).expect("h2 k4");
233 assert_eq!(t.vcount(), 17);
234 assert_eq!(t.ecount(), 16);
235 }
236}
237
238#[cfg(all(test, feature = "proptest-harness"))]
239mod proptests {
240 use super::*;
241 use proptest::prelude::*;
242
243 proptest! {
244 #[test]
246 fn edge_count_is_n_minus_one(h in 1u32..=4, k in 2u32..=4) {
247 let t = regular_tree(h, k, TreeMode::Out).unwrap();
248 let n = t.vcount();
249 prop_assert_eq!(t.ecount(), (n as usize) - 1);
250 }
251
252 #[test]
254 fn out_and_in_endpoints_swap(h in 1u32..=3, k in 2u32..=3) {
255 let out = regular_tree(h, k, TreeMode::Out).unwrap();
256 let inn = regular_tree(h, k, TreeMode::In).unwrap();
257 prop_assert_eq!(out.vcount(), inn.vcount());
258 prop_assert_eq!(out.ecount(), inn.ecount());
259 let m = u32::try_from(out.ecount()).unwrap();
260 for e in 0..m {
261 let (a, b) = out.edge(e).unwrap();
262 let (c, d) = inn.edge(e).unwrap();
263 prop_assert_eq!((a, b), (d, c));
264 }
265 }
266
267 #[test]
269 fn root_out_degree_equals_k(h in 1u32..=3, k in 2u32..=4) {
270 let t = regular_tree(h, k, TreeMode::Out).unwrap();
271 let m = u32::try_from(t.ecount()).unwrap();
272 let mut root_deg = 0u32;
273 for e in 0..m {
274 let (u, _v) = t.edge(e).unwrap();
275 if u == 0 { root_deg += 1; }
276 }
277 prop_assert_eq!(root_deg, k);
278 }
279 }
280}