rust_igraph/algorithms/constructors/
kautz.rs1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
35
36pub fn kautz(m: u32, n: u32) -> IgraphResult<Graph> {
65 if n == 0 {
68 return kautz_n_zero(m);
69 }
70 if m == 0 {
73 return Graph::new(0, true);
74 }
75
76 let dims = KautzDims::compute(m, n)?;
77 let (index1, index2) = build_kautz_indices(m, &dims)?;
78 let edges = emit_kautz_arcs(m, &dims, &index1, &index2);
79
80 let mut g = Graph::new(dims.vcount, true)?;
81 g.add_edges(edges)?;
82 Ok(g)
83}
84
85fn kautz_n_zero(m: u32) -> IgraphResult<Graph> {
89 let m_plus_1 = m.checked_add(1).ok_or_else(|| {
90 IgraphError::InvalidArgument(format!("kautz: m + 1 overflows u32 (m = {m})"))
91 })?;
92 let mp1_us = usize::try_from(m_plus_1).map_err(|_| {
93 IgraphError::InvalidArgument(format!("kautz: m + 1 = {m_plus_1} cannot fit usize"))
94 })?;
95 let ec = mp1_us
96 .checked_mul(mp1_us.saturating_sub(1))
97 .ok_or_else(|| {
98 IgraphError::InvalidArgument(format!("kautz: (m+1)·m overflows usize (m = {m})"))
99 })?;
100 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ec);
101 for i in 0..m_plus_1 {
102 for j in 0..m_plus_1 {
103 if i != j {
104 edges.push((i, j));
105 }
106 }
107 }
108 let mut g = Graph::new(m_plus_1, true)?;
109 g.add_edges(edges)?;
110 Ok(g)
111}
112
113struct KautzDims {
116 m_plus_1: u32,
117 vcount: u32,
118 allstrings: u32,
119 vcount_us: usize,
120 allstrings_us: usize,
121 ecount: usize,
122 n_us: usize,
123 n_plus_1_us: usize,
124}
125
126impl KautzDims {
127 fn compute(m: u32, n: u32) -> IgraphResult<Self> {
128 let m_plus_1 = m.checked_add(1).ok_or_else(|| {
129 IgraphError::InvalidArgument(format!("kautz: m + 1 overflows u32 (m = {m})"))
130 })?;
131 let n_plus_1 = n.checked_add(1).ok_or_else(|| {
132 IgraphError::InvalidArgument(format!("kautz: n + 1 overflows u32 (n = {n})"))
133 })?;
134 let m_to_n = m.checked_pow(n).ok_or_else(|| {
135 IgraphError::InvalidArgument(format!("kautz: m^n overflows u32 (m = {m}, n = {n})"))
136 })?;
137 let vcount = m_plus_1.checked_mul(m_to_n).ok_or_else(|| {
138 IgraphError::InvalidArgument(format!(
139 "kautz: (m+1)·m^n overflows u32 (m = {m}, n = {n})"
140 ))
141 })?;
142 let allstrings = m_plus_1.checked_pow(n_plus_1).ok_or_else(|| {
143 IgraphError::InvalidArgument(format!(
144 "kautz: (m+1)^(n+1) overflows u32 (m = {m}, n = {n})"
145 ))
146 })?;
147 let vcount_us = usize::try_from(vcount).map_err(|_| {
148 IgraphError::InvalidArgument(format!("kautz: vcount {vcount} cannot fit usize"))
149 })?;
150 let allstrings_us = usize::try_from(allstrings).map_err(|_| {
151 IgraphError::InvalidArgument(format!("kautz: allstrings {allstrings} cannot fit usize"))
152 })?;
153 let m_us = usize::try_from(m)
154 .map_err(|_| IgraphError::InvalidArgument(format!("kautz: m {m} cannot fit usize")))?;
155 let n_us = usize::try_from(n)
156 .map_err(|_| IgraphError::InvalidArgument(format!("kautz: n {n} cannot fit usize")))?;
157 let n_plus_1_us = usize::try_from(n_plus_1).map_err(|_| {
158 IgraphError::InvalidArgument(format!("kautz: n + 1 = {n_plus_1} cannot fit usize"))
159 })?;
160 let ecount = vcount_us.checked_mul(m_us).ok_or_else(|| {
161 IgraphError::InvalidArgument(format!(
162 "kautz: m·vcount overflows usize (m = {m}, n = {n})"
163 ))
164 })?;
165 Ok(Self {
166 m_plus_1,
167 vcount,
168 allstrings,
169 vcount_us,
170 allstrings_us,
171 ecount,
172 n_us,
173 n_plus_1_us,
174 })
175 }
176}
177
178fn build_kautz_indices(m: u32, dims: &KautzDims) -> IgraphResult<(Vec<u32>, Vec<u32>)> {
182 let mut table: Vec<u32> = vec![0; dims.n_plus_1_us];
185 let mut weight: u64 = 1;
186 for i in (0..dims.n_plus_1_us).rev() {
187 table[i] = u32::try_from(weight).map_err(|_| {
188 IgraphError::InvalidArgument(format!(
189 "kautz: table[{i}] overflows u32 (internal invariant)"
190 ))
191 })?;
192 weight = weight.saturating_mul(u64::from(dims.m_plus_1));
193 }
194
195 let mut index1: Vec<u32> = vec![0; dims.allstrings_us];
196 let mut index2: Vec<u32> = vec![0; dims.vcount_us];
197 let mut digits: Vec<u32> = vec![0; dims.n_plus_1_us];
198
199 let mut actb: usize = 0;
200 let mut actvalue: u32 = 0;
201 let mut idx: usize = 0;
202
203 loop {
204 let mut z: u32 = u32::from(digits[actb] == 0);
208 for k in (actb + 1)..=dims.n_us {
209 digits[k] = z;
210 actvalue += z * table[k];
211 z = 1 - z;
212 }
213 actb = dims.n_us;
214
215 index1[actvalue as usize] = u32::try_from(idx + 1).map_err(|_| {
216 IgraphError::InvalidArgument(
217 "kautz: idx + 1 overflows u32 (internal invariant)".to_string(),
218 )
219 })?;
220 index2[idx] = actvalue;
221 idx += 1;
222 if idx >= dims.vcount_us {
223 break;
224 }
225
226 let advanced = advance_kautz_cursor(m, &table, &mut digits, &mut actb, &mut actvalue);
228 if !advanced {
229 break;
232 }
233 }
234
235 Ok((index1, index2))
236}
237
238fn advance_kautz_cursor(
242 m: u32,
243 table: &[u32],
244 digits: &mut [u32],
245 actb: &mut usize,
246 actvalue: &mut u32,
247) -> bool {
248 loop {
249 let mut next = digits[*actb] + 1;
250 if *actb != 0 && digits[*actb - 1] == next {
251 next += 1;
252 }
253 if next <= m {
254 *actvalue += (next - digits[*actb]) * table[*actb];
255 digits[*actb] = next;
256 return true;
257 }
258 *actvalue -= digits[*actb] * table[*actb];
259 if *actb == 0 {
260 return false;
261 }
262 *actb -= 1;
263 }
264}
265
266fn emit_kautz_arcs(
270 m: u32,
271 dims: &KautzDims,
272 index1: &[u32],
273 index2: &[u32],
274) -> Vec<(VertexId, VertexId)> {
275 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(dims.ecount);
276 let m_plus_1_u64 = u64::from(dims.m_plus_1);
277 let allstrings_u64 = u64::from(dims.allstrings);
278 for i in 0..dims.vcount {
279 let fromvalue = index2[i as usize];
280 let lastdigit = fromvalue % dims.m_plus_1;
281 let basis_u64 = (u64::from(fromvalue) * m_plus_1_u64) % allstrings_u64;
284 #[allow(clippy::cast_possible_truncation)]
285 let basis = basis_u64 as u32;
286 for digit in 0..=m {
287 if digit == lastdigit {
288 continue;
289 }
290 let tovalue = basis + digit;
291 let sentinel = index1[tovalue as usize];
292 if sentinel == 0 {
293 continue;
294 }
295 edges.push((i, sentinel - 1));
296 }
297 }
298 edges
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use std::collections::BTreeSet;
305
306 fn dump_arcs(g: &Graph) -> Vec<(VertexId, VertexId)> {
307 let ec = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
308 let mut out = Vec::with_capacity(g.ecount());
309 for e in 0..ec {
310 let u = g.edge_source(e).expect("edge in range");
311 let v = g.edge_target(e).expect("edge in range");
312 out.push((u, v));
313 }
314 out
315 }
316
317 #[test]
318 fn n_zero_m_zero_singleton() {
319 let g = kautz(0, 0).expect("K(0,0)");
321 assert_eq!(g.vcount(), 1);
322 assert_eq!(g.ecount(), 0);
323 assert!(g.is_directed());
324 }
325
326 #[test]
327 fn n_zero_m_five_directed_k6() {
328 let g = kautz(5, 0).expect("K(5,0)");
330 assert_eq!(g.vcount(), 6);
331 assert_eq!(g.ecount(), 30);
332 let arcs: BTreeSet<_> = dump_arcs(&g).into_iter().collect();
333 let mut expected = BTreeSet::new();
334 for i in 0u32..6 {
335 for j in 0u32..6 {
336 if i != j {
337 expected.insert((i, j));
338 }
339 }
340 }
341 assert_eq!(arcs, expected);
342 }
343
344 #[test]
345 fn m_zero_n_ten_empty() {
346 let g = kautz(0, 10).expect("K(0,10)");
347 assert_eq!(g.vcount(), 0);
348 assert_eq!(g.ecount(), 0);
349 assert!(g.is_directed());
350 }
351
352 #[test]
353 fn k_2_1_counts() {
354 let g = kautz(2, 1).expect("K(2,1)");
355 assert_eq!(g.vcount(), 6);
356 assert_eq!(g.ecount(), 12);
357 assert!(g.is_directed());
358 }
359
360 #[test]
361 fn k_2_1_in_out_degrees() {
362 let g = kautz(2, 1).expect("K(2,1)");
364 let mut out_deg = [0u32; 6];
365 let mut in_deg = [0u32; 6];
366 for (u, v) in dump_arcs(&g) {
367 out_deg[u as usize] += 1;
368 in_deg[v as usize] += 1;
369 }
370 for (v, (&o, &i)) in out_deg.iter().zip(in_deg.iter()).enumerate() {
371 assert_eq!(o, 2, "out-degree at {v}");
372 assert_eq!(i, 2, "in-degree at {v}");
373 }
374 }
375
376 #[test]
377 fn k_3_2_counts() {
378 let g = kautz(3, 2).expect("K(3,2)");
380 assert_eq!(g.vcount(), 36);
381 assert_eq!(g.ecount(), 108);
382 }
383
384 #[test]
385 fn k_2_2_counts() {
386 let g = kautz(2, 2).expect("K(2,2)");
388 assert_eq!(g.vcount(), 12);
389 assert_eq!(g.ecount(), 24);
390 }
391
392 #[test]
393 fn vcount_overflow_rejected() {
394 let err = kautz(2, 31).expect_err("overflow expected");
396 assert!(matches!(err, IgraphError::InvalidArgument(_)));
397 }
398}
399
400#[cfg(all(test, feature = "proptest-harness"))]
401mod proptest_invariants {
402 use super::*;
403 use proptest::prelude::*;
404 use std::collections::BTreeSet;
405
406 proptest! {
407 #![proptest_config(ProptestConfig::with_cases(48))]
408
409 #[test]
412 fn vcount_and_ecount_are_exact(m in 1u32..=5, n in 1u32..=4) {
413 let Some(m_to_n) = m.checked_pow(n) else { return Ok(()); };
414 let Some(vcount) = (m + 1).checked_mul(m_to_n) else { return Ok(()); };
415 let Some(ecount) = vcount.checked_mul(m) else { return Ok(()); };
416 let g = kautz(m, n).expect("valid input");
417 prop_assert_eq!(g.vcount(), vcount);
418 prop_assert_eq!(g.ecount() as u64, u64::from(ecount));
419 prop_assert!(g.is_directed());
420 }
421
422 #[test]
424 fn vertex_in_and_out_degree_equals_m(m in 1u32..=4, n in 1u32..=3) {
425 let g = kautz(m, n).expect("valid input");
426 let vc = usize::try_from(g.vcount()).unwrap();
427 let mut out_deg = vec![0u32; vc];
428 let mut in_deg = vec![0u32; vc];
429 let ec = u32::try_from(g.ecount()).unwrap();
430 for e in 0..ec {
431 let u = g.edge_source(e).expect("edge in range");
432 let v = g.edge_target(e).expect("edge in range");
433 out_deg[u as usize] += 1;
434 in_deg[v as usize] += 1;
435 }
436 for v in 0..vc {
437 prop_assert_eq!(out_deg[v], m);
438 prop_assert_eq!(in_deg[v], m);
439 }
440 }
441
442 #[test]
445 fn loopless_and_simple(m in 1u32..=4, n in 1u32..=3) {
446 let g = kautz(m, n).expect("valid input");
447 let ec = u32::try_from(g.ecount()).unwrap();
448 let mut seen: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
449 for e in 0..ec {
450 let u = g.edge_source(e).expect("edge in range");
451 let v = g.edge_target(e).expect("edge in range");
452 prop_assert_ne!(u, v, "Kautz graph must not contain self-loops");
453 prop_assert!(seen.insert((u, v)), "duplicate arc ({u}, {v})");
454 }
455 }
456 }
457}