1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum EdgeTypeFilter {
12 Simple,
14 LoopsSimple,
16 Multi,
18 LoopsMulti,
20}
21
22pub fn is_graphical(
50 out_degrees: &[u32],
51 in_degrees: Option<&[u32]>,
52 edge_types: EdgeTypeFilter,
53) -> bool {
54 match in_degrees {
55 None => is_graphical_undirected(out_degrees, edge_types),
56 Some(in_deg) => is_graphical_directed(out_degrees, in_deg, edge_types),
57 }
58}
59
60pub fn is_bigraphical(degrees1: &[u32], degrees2: &[u32], edge_types: EdgeTypeFilter) -> bool {
84 match edge_types {
85 EdgeTypeFilter::Multi | EdgeTypeFilter::LoopsMulti => bigraphical_multi(degrees1, degrees2),
86 _ => bigraphical_simple(degrees1, degrees2),
87 }
88}
89
90fn is_graphical_undirected(degrees: &[u32], edge_types: EdgeTypeFilter) -> bool {
91 match edge_types {
92 EdgeTypeFilter::LoopsMulti => undirected_multi_loops(degrees),
93 EdgeTypeFilter::Multi => undirected_loopless_multi(degrees),
94 EdgeTypeFilter::LoopsSimple => undirected_loopy_simple(degrees),
95 EdgeTypeFilter::Simple => undirected_simple(degrees),
96 }
97}
98
99fn is_graphical_directed(
100 out_degrees: &[u32],
101 in_degrees: &[u32],
102 edge_types: EdgeTypeFilter,
103) -> bool {
104 if out_degrees.len() != in_degrees.len() {
105 return false;
106 }
107 match edge_types {
108 EdgeTypeFilter::LoopsMulti => directed_loopy_multi(out_degrees, in_degrees),
109 EdgeTypeFilter::Multi => directed_loopless_multi(out_degrees, in_degrees),
110 EdgeTypeFilter::LoopsSimple => bigraphical_simple(out_degrees, in_degrees),
111 EdgeTypeFilter::Simple => directed_simple(out_degrees, in_degrees),
112 }
113}
114
115fn undirected_multi_loops(degrees: &[u32]) -> bool {
118 let mut sum_parity: u64 = 0;
119 for &d in degrees {
120 sum_parity = (sum_parity + u64::from(d)) & 1;
121 }
122 sum_parity == 0
123}
124
125fn undirected_loopless_multi(degrees: &[u32]) -> bool {
126 if degrees.is_empty() {
127 return true;
128 }
129 let mut dsum: u64 = 0;
130 let mut dmax: u64 = 0;
131 for &d in degrees {
132 let d64 = u64::from(d);
133 dsum += d64;
134 if d64 > dmax {
135 dmax = d64;
136 }
137 }
138 dsum % 2 == 0 && dsum >= 2 * dmax
139}
140
141#[allow(
142 clippy::cast_possible_truncation,
143 clippy::cast_sign_loss,
144 clippy::cast_possible_wrap,
145 clippy::many_single_char_names
146)]
147fn undirected_loopy_simple(degrees: &[u32]) -> bool {
148 let n = degrees.len();
149 if n == 0 {
150 return true;
151 }
152
153 if !undirected_multi_loops(degrees) {
154 return false;
155 }
156
157 let n_i64 = n as i64;
158 let mut num_degs = vec![0i64; n + 2];
159 for &d in degrees {
160 let d_usize = d as usize;
161 if d_usize > n + 1 {
162 return false;
163 }
164 num_degs[d_usize] += 1;
165 }
166
167 for d in (0..=n).rev() {
168 num_degs[d] += num_degs[d + 1];
169 }
170
171 let mut wd: i64 = 0;
172 let mut kd: i64 = n_i64 + 1;
173 let mut w: i64 = n_i64 - 1;
174 let mut b: i64 = 0;
175 let mut s: i64 = 0;
176 let mut c: i64 = 0;
177
178 for k in 0..n_i64 {
179 while k >= num_degs[kd as usize] {
180 kd -= 1;
181 }
182 b += kd;
183 c += w;
184 while w > k {
185 while wd <= n_i64 && w < num_degs[(wd + 1) as usize] {
186 wd += 1;
187 }
188 if wd > k + 1 {
189 break;
190 }
191 s += wd;
192 c -= k + 1;
193 w -= 1;
194 }
195 if b > c + s + 2 * (k + 1) {
196 return false;
197 }
198 if w == k {
199 break;
200 }
201 }
202 true
203}
204
205#[allow(
206 clippy::cast_possible_truncation,
207 clippy::cast_possible_wrap,
208 clippy::cast_sign_loss,
209 clippy::similar_names
210)]
211fn undirected_simple(degrees: &[u32]) -> bool {
212 let p = degrees.len();
213 if p == 0 {
214 return true;
215 }
216
217 let mut num_degs = vec![0i64; p];
218 let mut deg_min: u64 = u64::from(u32::MAX);
219 let mut deg_max: u64 = 0;
220 let mut dsum: u64 = 0;
221 let mut nonzero_count: i64 = 0;
222
223 for &d in degrees {
224 let d64 = u64::from(d);
225 if d64 >= p as u64 {
226 return false;
227 }
228 if d > 0 {
229 if d64 > deg_max {
230 deg_max = d64;
231 }
232 if d64 < deg_min {
233 deg_min = d64;
234 }
235 dsum += d64;
236 nonzero_count += 1;
237 num_degs[d as usize] += 1;
238 }
239 }
240
241 if dsum % 2 != 0 {
242 return false;
243 }
244 if nonzero_count == 0 {
245 return true;
246 }
247
248 let zv_arg = (deg_max + deg_min + 1) as i64;
250 let mut zverovich_bound = (zv_arg * zv_arg) / 4;
251 if deg_min % 2 == 1 || (deg_max + deg_min) % 4 == 1 {
252 zverovich_bound -= 1;
253 }
254 if (deg_min as i64) * nonzero_count >= zverovich_bound {
255 return true;
256 }
257
258 let mut k: i64 = 0;
260 let mut sum_deg: i64 = 0;
261 let mut sum_freq: i64 = 0;
262 let mut sum_ifreq: i64 = 0;
263
264 let deg_min_i = deg_min as i64;
265 let deg_max_i = deg_max as i64;
266
267 let mut dk = deg_max_i;
268 while dk >= deg_min_i {
269 if dk < k + 1 {
270 return true;
271 }
272
273 let mut run_size = num_degs[dk as usize];
274 if run_size > 0 {
275 if dk < k + run_size {
276 run_size = dk - k;
277 }
278 sum_deg += run_size * dk;
279 for v in 0..run_size {
280 sum_freq += num_degs[(k + v) as usize];
281 sum_ifreq += (k + v) * num_degs[(k + v) as usize];
282 }
283 k += run_size;
284 if sum_deg > k * (nonzero_count - 1) - k * sum_freq + sum_ifreq {
285 return false;
286 }
287 }
288 dk -= 1;
289 }
290
291 true
292}
293
294fn directed_loopy_multi(out_degrees: &[u32], in_degrees: &[u32]) -> bool {
297 let mut sumdiff: i64 = 0;
298 for (&dout, &din) in out_degrees.iter().zip(in_degrees.iter()) {
299 sumdiff += i64::from(din) - i64::from(dout);
300 }
301 sumdiff == 0
302}
303
304fn directed_loopless_multi(out_degrees: &[u32], in_degrees: &[u32]) -> bool {
305 let mut sumin: u64 = 0;
306 let mut sumout: u64 = 0;
307 let mut dmax: u64 = 0;
308 for (&dout, &din) in out_degrees.iter().zip(in_degrees.iter()) {
309 let d = u64::from(dout) + u64::from(din);
310 sumin += u64::from(din);
311 sumout += u64::from(dout);
312 if d > dmax {
313 dmax = d;
314 }
315 }
316 sumin == sumout && sumout >= dmax
317}
318
319#[allow(
320 clippy::cast_possible_truncation,
321 clippy::cast_possible_wrap,
322 clippy::cast_sign_loss
323)]
324fn directed_simple(out_degrees: &[u32], in_degrees: &[u32]) -> bool {
325 if !directed_loopy_multi(out_degrees, in_degrees) {
326 return false;
327 }
328
329 let vcount = out_degrees.len();
330 if vcount == 0 {
331 return true;
332 }
333 let vc_i64 = vcount as i64;
334
335 let mut in_degree_cumcounts = vec![0usize; vcount + 1];
337 for idx in 0..vcount {
338 let indeg = in_degrees[idx] as usize;
339 let outdeg = out_degrees[idx] as usize;
340 if indeg >= vcount || outdeg >= vcount {
341 return false;
342 }
343 in_degree_cumcounts[indeg + 1] += 1;
344 }
345
346 for indeg in 0..vcount {
347 in_degree_cumcounts[indeg + 1] += in_degree_cumcounts[indeg];
348 }
349
350 let mut sorted_out = vec![0i64; vcount];
351 let mut sorted_in = vec![0i64; vcount];
352 let mut in_degree_counts = vec![0usize; vcount];
353
354 for idx in 0..vcount {
355 let outdeg = out_degrees[idx] as usize;
356 let indeg = in_degrees[idx] as usize;
357 let pos = in_degree_cumcounts[indeg] + in_degree_counts[indeg];
358 sorted_out[vcount - pos - 1] = outdeg as i64;
359 sorted_in[vcount - pos - 1] = indeg as i64;
360 in_degree_counts[indeg] += 1;
361 }
362
363 let mut left_pq = vec![0i64; vcount];
365 let mut right_pq = vec![0i64; vcount];
366 let mut left_sum: i64 = 0;
367 let mut right_sum: i64 = 0;
368 let mut left_pq_size: i64 = 0;
369 let mut right_pq_size: i64 = vc_i64;
370 let mut left_cursor: i64 = 0;
371 let mut right_cursor: i64 = 0;
372
373 for idx in 0..vcount {
374 right_pq[sorted_out[idx] as usize] += 1;
375 }
376
377 let mut lhs: i64 = 0;
378 for step in 0..vc_i64 {
379 let step_u = step as usize;
380 lhs += sorted_in[step_u];
381
382 if sorted_out[step_u] < step {
383 left_sum += sorted_out[step_u];
384 } else {
385 left_pq[sorted_out[step_u] as usize] += 1;
386 left_pq_size += 1;
387 }
388 while left_cursor < step {
389 let lc = left_cursor as usize;
390 while left_pq[lc] > 0 {
391 left_pq[lc] -= 1;
392 left_pq_size -= 1;
393 left_sum += left_cursor;
394 }
395 left_cursor += 1;
396 }
397
398 while right_cursor < step + 1 {
399 let rc = right_cursor as usize;
400 while right_pq[rc] > 0 {
401 right_pq[rc] -= 1;
402 right_pq_size -= 1;
403 right_sum += right_cursor;
404 }
405 right_cursor += 1;
406 }
407 if sorted_out[step_u] < step + 1 {
408 right_sum -= sorted_out[step_u];
409 } else {
410 right_pq[sorted_out[step_u] as usize] -= 1;
411 right_pq_size -= 1;
412 }
413
414 let rhs = left_sum + step * left_pq_size + right_sum + (step + 1) * right_pq_size;
415 if lhs > rhs {
416 return false;
417 }
418 }
419 true
420}
421
422fn bigraphical_multi(degrees1: &[u32], degrees2: &[u32]) -> bool {
425 let sum1: u64 = degrees1.iter().map(|&d| u64::from(d)).sum();
426 let sum2: u64 = degrees2.iter().map(|&d| u64::from(d)).sum();
427 sum1 == sum2
428}
429
430#[allow(
431 clippy::cast_possible_truncation,
432 clippy::cast_possible_wrap,
433 clippy::cast_sign_loss
434)]
435fn bigraphical_simple(degrees1: &[u32], degrees2: &[u32]) -> bool {
436 let (mut d1, mut d2) = (degrees1, degrees2);
437 let (mut n1, mut n2) = (d1.len(), d2.len());
438
439 if n1 == 0 && n2 == 0 {
440 return true;
441 }
442
443 if !bigraphical_multi(d1, d2) {
444 return false;
445 }
446
447 if n2 < n1 {
449 std::mem::swap(&mut d1, &mut d2);
450 std::mem::swap(&mut n1, &mut n2);
451 }
452
453 let mut deg_freq1 = vec![0i64; n2 + 1];
454 let mut deg_freq2 = vec![0i64; n1 + 1];
455
456 for &d in d1 {
457 let du = d as usize;
458 if du > n2 {
459 return false;
460 }
461 deg_freq1[du] += 1;
462 }
463 for &d in d2 {
464 let du = d as usize;
465 if du > n1 {
466 return false;
467 }
468 deg_freq2[du] += 1;
469 }
470
471 let mut lhs_sum: i64 = 0;
473 let mut partial_rhs_sum: i64 = 0;
474 let mut partial_rhs_count: i64 = 0;
475 let mut b: i64 = 0;
476 let mut k: i64 = -1;
477 let n2_i64 = n2 as i64;
478
479 let mut a = n2 as i64;
480 while a >= 0 {
481 let acount = deg_freq1[a as usize];
482 lhs_sum += a * acount;
483 k += acount;
484
485 while b <= k + 1 {
486 let bu = b as usize;
487 let bcount = deg_freq2[bu];
488 partial_rhs_sum += b * bcount;
489 partial_rhs_count += bcount;
490 b += 1;
491 }
492
493 if lhs_sum > partial_rhs_sum + (n2_i64 - partial_rhs_count) * (k + 1) {
494 return false;
495 }
496 a -= 1;
497 }
498
499 true
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505
506 #[test]
509 fn empty_sequence_is_graphical() {
510 assert!(is_graphical(&[], None, EdgeTypeFilter::Simple));
511 }
512
513 #[test]
514 fn single_zero_degree() {
515 assert!(is_graphical(&[0], None, EdgeTypeFilter::Simple));
516 }
517
518 #[test]
519 fn all_zeros() {
520 assert!(is_graphical(&[0, 0, 0], None, EdgeTypeFilter::Simple));
521 }
522
523 #[test]
524 fn star_k14_simple() {
525 assert!(is_graphical(&[4, 1, 1, 1, 1], None, EdgeTypeFilter::Simple));
526 }
527
528 #[test]
529 fn complete_k4_simple() {
530 assert!(is_graphical(&[3, 3, 3, 3], None, EdgeTypeFilter::Simple));
531 }
532
533 #[test]
534 fn cycle_c5_simple() {
535 assert!(is_graphical(&[2, 2, 2, 2, 2], None, EdgeTypeFilter::Simple));
536 }
537
538 #[test]
539 fn odd_sum_not_graphical() {
540 assert!(!is_graphical(&[3, 1, 1], None, EdgeTypeFilter::Simple));
541 }
542
543 #[test]
544 fn degree_too_large_simple() {
545 assert!(!is_graphical(
546 &[5, 1, 1, 1, 1],
547 None,
548 EdgeTypeFilter::Simple
549 ));
550 }
551
552 #[test]
553 fn erdos_gallai_failure() {
554 assert!(!is_graphical(&[4, 4, 1, 1], None, EdgeTypeFilter::Simple));
556 }
557
558 #[test]
559 fn path_p3_simple() {
560 assert!(is_graphical(&[1, 2, 1], None, EdgeTypeFilter::Simple));
561 }
562
563 #[test]
566 fn odd_sum_with_loops_multi() {
567 assert!(!is_graphical(&[3, 2], None, EdgeTypeFilter::LoopsMulti));
568 }
569
570 #[test]
571 fn even_sum_with_loops_multi() {
572 assert!(is_graphical(&[3, 3], None, EdgeTypeFilter::LoopsMulti));
573 }
574
575 #[test]
578 fn loopless_multi_basic() {
579 assert!(is_graphical(&[4, 2, 2], None, EdgeTypeFilter::Multi));
580 }
581
582 #[test]
583 fn loopless_multi_max_too_large() {
584 assert!(!is_graphical(&[5, 1, 0], None, EdgeTypeFilter::Multi));
586 }
587
588 #[test]
591 fn loopy_simple_basic() {
592 assert!(is_graphical(
594 &[3, 3, 2, 2],
595 None,
596 EdgeTypeFilter::LoopsSimple
597 ));
598 }
599
600 #[test]
601 fn loopy_simple_degree_too_large() {
602 assert!(!is_graphical(
604 &[10, 1, 1],
605 None,
606 EdgeTypeFilter::LoopsSimple
607 ));
608 }
609
610 #[test]
613 fn directed_simple_basic() {
614 assert!(is_graphical(
616 &[1, 1, 1],
617 Some(&[1, 1, 1]),
618 EdgeTypeFilter::Simple
619 ));
620 }
621
622 #[test]
623 fn directed_simple_star() {
624 assert!(is_graphical(
626 &[3, 0, 0, 0],
627 Some(&[0, 1, 1, 1]),
628 EdgeTypeFilter::Simple
629 ));
630 }
631
632 #[test]
633 fn directed_sum_mismatch() {
634 assert!(!is_graphical(
635 &[2, 1],
636 Some(&[1, 1]),
637 EdgeTypeFilter::Simple
638 ));
639 }
640
641 #[test]
642 fn directed_length_mismatch() {
643 assert!(!is_graphical(&[1, 1], Some(&[1]), EdgeTypeFilter::Simple));
644 }
645
646 #[test]
647 fn directed_loopy_multi_basic() {
648 assert!(is_graphical(
649 &[2, 1],
650 Some(&[1, 2]),
651 EdgeTypeFilter::LoopsMulti
652 ));
653 }
654
655 #[test]
656 fn directed_loopless_multi_basic() {
657 assert!(is_graphical(&[1, 1], Some(&[1, 1]), EdgeTypeFilter::Multi));
658 }
659
660 #[test]
661 fn directed_loopless_multi_max_too_large() {
662 assert!(is_graphical(&[3, 0], Some(&[0, 3]), EdgeTypeFilter::Multi));
664 assert!(is_graphical(&[4, 0], Some(&[0, 4]), EdgeTypeFilter::Multi));
666 }
667
668 #[test]
669 fn directed_degree_too_large_simple() {
670 assert!(!is_graphical(
672 &[3, 0, 0],
673 Some(&[0, 0, 3]),
674 EdgeTypeFilter::Simple
675 ));
676 }
677
678 #[test]
681 fn bigraphical_empty() {
682 assert!(is_bigraphical(&[], &[], EdgeTypeFilter::Simple));
683 }
684
685 #[test]
686 fn bigraphical_k23_simple() {
687 assert!(is_bigraphical(&[3, 3], &[2, 2, 2], EdgeTypeFilter::Simple));
688 }
689
690 #[test]
691 fn bigraphical_sum_mismatch() {
692 assert!(!is_bigraphical(&[3, 3], &[2, 2, 1], EdgeTypeFilter::Multi));
693 }
694
695 #[test]
696 fn bigraphical_degree_exceeds_partition() {
697 assert!(!is_bigraphical(&[4, 0], &[2, 2], EdgeTypeFilter::Simple));
699 }
700
701 #[test]
702 fn bigraphical_multi_basic() {
703 assert!(is_bigraphical(&[5, 5], &[5, 5], EdgeTypeFilter::Multi));
704 }
705
706 #[test]
707 fn bigraphical_simple_asymmetric() {
708 assert!(is_bigraphical(&[3], &[1, 1, 1], EdgeTypeFilter::Simple));
710 }
711
712 #[test]
713 fn bigraphical_gale_ryser_failure() {
714 assert!(!is_bigraphical(&[3, 3], &[1, 1, 1], EdgeTypeFilter::Simple));
716 }
717
718 #[test]
721 fn igraph_test_simple_sequences() {
722 assert!(is_graphical(&[0], None, EdgeTypeFilter::Simple));
724 assert!(is_graphical(&[0, 0], None, EdgeTypeFilter::Simple));
725 assert!(is_graphical(&[1, 1], None, EdgeTypeFilter::Simple));
726 assert!(is_graphical(
727 &[1, 3, 2, 4, 3, 3, 2, 2],
728 None,
729 EdgeTypeFilter::Simple
730 ));
731 }
732
733 #[test]
734 fn igraph_test_non_graphical_simple() {
735 assert!(!is_graphical(&[1], None, EdgeTypeFilter::Simple));
736 assert!(!is_graphical(&[1, 1, 1], None, EdgeTypeFilter::Simple));
737 assert!(!is_graphical(
738 &[1, 3, 2, 4, 3, 3, 2, 3],
739 None,
740 EdgeTypeFilter::Simple
741 ));
742 }
743
744 #[test]
745 fn directed_fulkerson_complete() {
746 assert!(is_graphical(
748 &[3, 3, 3, 3],
749 Some(&[3, 3, 3, 3]),
750 EdgeTypeFilter::Simple
751 ));
752 }
753
754 #[test]
755 fn directed_fulkerson_tournament() {
756 assert!(is_graphical(
758 &[3, 2, 1, 0],
759 Some(&[0, 1, 2, 3]),
760 EdgeTypeFilter::Simple
761 ));
762 }
763}
764
765#[cfg(all(test, feature = "proptest-harness"))]
766mod proptests {
767 use super::*;
768 use proptest::prelude::*;
769
770 proptest! {
771 #[test]
772 fn all_zeros_always_graphical(n in 0usize..50) {
773 let degrees = vec![0u32; n];
774 prop_assert!(is_graphical(°rees, None, EdgeTypeFilter::Simple));
775 prop_assert!(is_graphical(°rees, None, EdgeTypeFilter::Multi));
776 prop_assert!(is_graphical(°rees, None, EdgeTypeFilter::LoopsSimple));
777 prop_assert!(is_graphical(°rees, None, EdgeTypeFilter::LoopsMulti));
778 }
779
780 #[test]
781 fn regular_even_is_graphical(d in 1u32..10, n in 2usize..20) {
782 let degrees = vec![d; n];
784 let sum = d as u64 * n as u64;
785 if sum % 2 == 0 && d < n as u32 {
786 prop_assert!(is_graphical(°rees, None, EdgeTypeFilter::Multi));
788 prop_assert!(is_graphical(°rees, None, EdgeTypeFilter::LoopsMulti));
790 }
791 }
792
793 #[test]
794 fn simple_implies_multi(
795 degrees in proptest::collection::vec(0u32..20, 0..20)
796 ) {
797 if is_graphical(°rees, None, EdgeTypeFilter::Simple) {
799 prop_assert!(is_graphical(°rees, None, EdgeTypeFilter::Multi));
800 }
801 }
802
803 #[test]
804 fn multi_implies_loops_multi(
805 degrees in proptest::collection::vec(0u32..20, 0..20)
806 ) {
807 if is_graphical(°rees, None, EdgeTypeFilter::Multi) {
808 prop_assert!(is_graphical(°rees, None, EdgeTypeFilter::LoopsMulti));
809 }
810 }
811
812 #[test]
813 fn directed_sum_parity(
814 out_deg in proptest::collection::vec(0u32..10, 1..15),
815 in_deg in proptest::collection::vec(0u32..10, 1..15),
816 ) {
817 let n = out_deg.len().min(in_deg.len());
818 let out_d = &out_deg[..n];
819 let in_d = &in_deg[..n];
820 let sum_out: u64 = out_d.iter().map(|&x| u64::from(x)).sum();
821 let sum_in: u64 = in_d.iter().map(|&x| u64::from(x)).sum();
822 if sum_out != sum_in {
823 prop_assert!(!is_graphical(out_d, Some(in_d), EdgeTypeFilter::LoopsMulti));
824 }
825 }
826
827 #[test]
828 fn bigraphical_simple_implies_multi(
829 d1 in proptest::collection::vec(0u32..10, 0..15),
830 d2 in proptest::collection::vec(0u32..10, 0..15),
831 ) {
832 if is_bigraphical(&d1, &d2, EdgeTypeFilter::Simple) {
833 prop_assert!(is_bigraphical(&d1, &d2, EdgeTypeFilter::Multi));
834 }
835 }
836 }
837}