rust_igraph/algorithms/constructors/
de_bruijn.rs1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
34
35pub fn de_bruijn(m: u32, n: u32) -> IgraphResult<Graph> {
64 if n == 0 {
67 return Graph::new(1, true);
69 }
70 if m == 0 {
71 return Graph::new(0, true);
72 }
73
74 let vcount = m.checked_pow(n).ok_or_else(|| {
75 IgraphError::InvalidArgument(format!("de_bruijn: m^n overflows u32 (m = {m}, n = {n})"))
76 })?;
77
78 let vcount_us = usize::try_from(vcount).map_err(|_| {
81 IgraphError::InvalidArgument(format!(
82 "de_bruijn: vcount {vcount} cannot fit usize on this target"
83 ))
84 })?;
85 let m_us = usize::try_from(m).map_err(|_| {
86 IgraphError::InvalidArgument(format!("de_bruijn: m {m} cannot fit usize on this target"))
87 })?;
88 let ecount = vcount_us.checked_mul(m_us).ok_or_else(|| {
89 IgraphError::InvalidArgument(format!(
90 "de_bruijn: m^(n+1) overflows usize (m = {m}, n = {n})"
91 ))
92 })?;
93
94 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ecount);
95
96 let vcount_u64 = u64::from(vcount);
100 let m_u64 = u64::from(m);
101 for i in 0..vcount {
102 let basis = (u64::from(i) * m_u64) % vcount_u64;
103 for b in 0..m {
104 #[allow(clippy::cast_possible_truncation)]
107 let target = (basis + u64::from(b)) as u32;
108 edges.push((i, target));
109 }
110 }
111
112 let mut g = Graph::new(vcount, true)?;
113 g.add_edges(edges)?;
114 Ok(g)
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use std::collections::BTreeSet;
121
122 fn dump_arcs(g: &Graph) -> Vec<(VertexId, VertexId)> {
123 let ec = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
124 let mut out = Vec::with_capacity(g.ecount());
125 for e in 0..ec {
126 let u = g.edge_source(e).expect("edge in range");
128 let v = g.edge_target(e).expect("edge in range");
129 out.push((u, v));
130 }
131 out
132 }
133
134 fn vcount_us(g: &Graph) -> usize {
135 usize::try_from(g.vcount()).expect("vcount fits usize")
136 }
137
138 #[test]
139 fn n_zero_yields_singleton_directed() {
140 let g = de_bruijn(5, 0).expect("n=0 valid");
141 assert_eq!(g.vcount(), 1);
142 assert_eq!(g.ecount(), 0);
143 assert!(g.is_directed());
144 }
145
146 #[test]
147 fn m_zero_yields_null_directed() {
148 let g = de_bruijn(0, 4).expect("m=0 valid");
149 assert_eq!(g.vcount(), 0);
150 assert_eq!(g.ecount(), 0);
151 assert!(g.is_directed());
152 }
153
154 #[test]
155 fn m_one_n_one_is_single_self_loop() {
156 let g = de_bruijn(1, 1).expect("B(1,1) valid");
157 assert_eq!(g.vcount(), 1);
158 assert_eq!(g.ecount(), 1);
159 assert_eq!(dump_arcs(&g), vec![(0, 0)]);
160 }
161
162 #[test]
163 fn b_2_1_is_directed_k2_with_loops() {
164 let g = de_bruijn(2, 1).expect("B(2,1) valid");
166 assert_eq!(g.vcount(), 2);
167 assert_eq!(g.ecount(), 4);
168 let arcs: BTreeSet<_> = dump_arcs(&g).into_iter().collect();
169 let expected: BTreeSet<_> = [(0, 0), (0, 1), (1, 0), (1, 1)].into_iter().collect();
170 assert_eq!(arcs, expected);
171 }
172
173 #[test]
174 fn b_2_2_exact_arc_list() {
175 let g = de_bruijn(2, 2).expect("B(2,2) valid");
181 assert_eq!(g.vcount(), 4);
182 assert_eq!(g.ecount(), 8);
183 let expected = vec![
184 (0, 0),
185 (0, 1),
186 (1, 2),
187 (1, 3),
188 (2, 0),
189 (2, 1),
190 (3, 2),
191 (3, 3),
192 ];
193 assert_eq!(dump_arcs(&g), expected);
194 }
195
196 #[test]
197 fn b_3_2_exact_arc_count_and_degrees() {
198 let g = de_bruijn(3, 2).expect("B(3,2) valid");
200 assert_eq!(g.vcount(), 9);
201 assert_eq!(g.ecount(), 27);
202 let arcs = dump_arcs(&g);
205 let mut out_deg = [0u32; 9];
206 let mut in_deg = [0u32; 9];
207 for (u, v) in arcs {
208 out_deg[u as usize] += 1;
209 in_deg[v as usize] += 1;
210 }
211 for (v, (&o, &i)) in out_deg.iter().zip(in_deg.iter()).enumerate() {
212 assert_eq!(o, 3, "out-degree at {v}");
213 assert_eq!(i, 3, "in-degree at {v}");
214 }
215 }
216
217 #[test]
218 fn b_2_3_total_counts() {
219 let g = de_bruijn(2, 3).expect("B(2,3) valid");
220 assert_eq!(g.vcount(), 8);
221 assert_eq!(g.ecount(), 16);
222 }
223
224 #[test]
225 fn b_4_3_total_counts() {
226 let g = de_bruijn(4, 3).expect("B(4,3) valid");
227 assert_eq!(g.vcount(), 64);
228 assert_eq!(g.ecount(), 256);
229 }
230
231 #[test]
232 fn rewrite_rule_holds() {
233 let m: u32 = 3;
236 let n: u32 = 2;
237 let g = de_bruijn(m, n).expect("valid");
238 let vcount = g.vcount();
239 for (u, v) in dump_arcs(&g) {
240 let basis = (u * m) % vcount;
241 assert!(
242 v >= basis && v < basis + m,
243 "arc ({u} → {v}) violates rewrite (basis = {basis}, m = {m})"
244 );
245 }
246 }
247
248 #[test]
249 fn vcount_overflow_rejected() {
250 let err = de_bruijn(2, 32).expect_err("vcount overflow");
252 assert!(matches!(err, IgraphError::InvalidArgument(_)));
253 }
254
255 #[test]
256 fn out_degree_uniform_for_all_supported_inputs() {
257 for m in 1..=4u32 {
258 for n in 1..=3u32 {
259 let g = de_bruijn(m, n).expect("valid B(m, n)");
260 let vc = vcount_us(&g);
261 let expected_ecount = (m as usize).pow(n + 1);
262 assert_eq!(
263 g.ecount(),
264 expected_ecount,
265 "ecount mismatch for B({m}, {n})"
266 );
267 let arcs = dump_arcs(&g);
268 let mut out_deg = vec![0u32; vc];
269 for (u, _) in arcs {
270 out_deg[u as usize] += 1;
271 }
272 for (v, &d) in out_deg.iter().enumerate() {
273 assert_eq!(d, m, "out-degree mismatch at {v} in B({m}, {n})");
274 }
275 }
276 }
277 }
278
279 #[test]
280 fn in_degree_uniform_for_all_supported_inputs() {
281 for m in 1..=4u32 {
282 for n in 1..=3u32 {
283 let g = de_bruijn(m, n).expect("valid B(m, n)");
284 let vc = vcount_us(&g);
285 let arcs = dump_arcs(&g);
286 let mut in_deg = vec![0u32; vc];
287 for (_, v) in arcs {
288 in_deg[v as usize] += 1;
289 }
290 for (v, &d) in in_deg.iter().enumerate() {
291 assert_eq!(d, m, "in-degree mismatch at {v} in B({m}, {n})");
292 }
293 }
294 }
295 }
296}
297
298#[cfg(all(test, feature = "proptest-harness"))]
299mod proptest_invariants {
300 use super::*;
301 use proptest::prelude::*;
302
303 proptest! {
304 #![proptest_config(ProptestConfig::with_cases(64))]
305
306 #[test]
309 fn vcount_and_ecount_are_exact(m in 1u32..=5, n in 1u32..=4) {
310 let Some(vcount) = m.checked_pow(n) else { return Ok(()); };
312 let Some(ecount) = vcount.checked_mul(m) else { return Ok(()); };
313 let g = de_bruijn(m, n).expect("valid input");
314 prop_assert_eq!(g.vcount(), vcount);
315 prop_assert_eq!(g.ecount() as u64, u64::from(ecount));
316 }
317
318 #[test]
320 fn vertex_in_and_out_degree_equals_m(m in 1u32..=4, n in 1u32..=3) {
321 let g = de_bruijn(m, n).expect("valid input");
322 let vcount = g.vcount();
323 let vc = usize::try_from(vcount).unwrap();
324 let mut out_deg = vec![0u32; vc];
325 let mut in_deg = vec![0u32; vc];
326 let ec = u32::try_from(g.ecount()).unwrap();
327 for e in 0..ec {
328 let u = g.edge_source(e).expect("edge in range");
329 let v = g.edge_target(e).expect("edge in range");
330 out_deg[u as usize] += 1;
331 in_deg[v as usize] += 1;
332 }
333 for v in 0..vc {
334 prop_assert_eq!(out_deg[v], m);
335 prop_assert_eq!(in_deg[v], m);
336 }
337 }
338
339 #[test]
342 fn rewrite_rule_holds_for_every_arc(m in 1u32..=4, n in 1u32..=3) {
343 let g = de_bruijn(m, n).expect("valid input");
344 let vcount = g.vcount();
345 let ec = u32::try_from(g.ecount()).unwrap();
346 for e in 0..ec {
347 let u = g.edge_source(e).expect("edge in range");
348 let v = g.edge_target(e).expect("edge in range");
349 let basis = (u * m) % vcount;
350 prop_assert!(v >= basis && v < basis + m);
351 }
352 }
353 }
354}