1#![allow(
14 clippy::cast_possible_truncation,
15 clippy::cast_sign_loss,
16 clippy::too_many_lines,
17 clippy::similar_names,
18 clippy::module_name_repetitions
19)]
20
21use std::collections::BinaryHeap;
22
23use crate::IgraphResult;
24use crate::core::error::IgraphError;
25use crate::core::graph::{EdgeId, Graph, VertexId};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum GreedyColoringHeuristic {
30 ColoredNeighbors,
33 DSatur,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct BipartiteColoringResult {
43 pub valid: bool,
45 pub mode: Option<BipartiteEdgeDirection>,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum BipartiteEdgeDirection {
57 Out,
59 In,
61 All,
63}
64
65pub fn vertex_coloring_greedy(
100 graph: &Graph,
101 heuristic: GreedyColoringHeuristic,
102) -> IgraphResult<Vec<u32>> {
103 match heuristic {
104 GreedyColoringHeuristic::ColoredNeighbors => greedy_cn(graph),
105 GreedyColoringHeuristic::DSatur => greedy_dsatur(graph),
106 }
107}
108
109fn all_neighbors(graph: &Graph, v: VertexId) -> IgraphResult<Vec<VertexId>> {
112 if graph.is_directed() {
113 let mut out = graph.out_neighbors_vec(v)?;
114 let in_neis = graph.in_neighbors_vec(v)?;
115 out.extend(in_neis);
116 Ok(out)
117 } else {
118 Ok(graph.neighbors_iter(v)?.collect())
119 }
120}
121
122fn all_neighbors_simple(graph: &Graph, v: VertexId) -> IgraphResult<Vec<VertexId>> {
125 let mut neis = all_neighbors(graph, v)?;
126 neis.retain(|&n| n != v);
127 neis.sort_unstable();
128 neis.dedup();
129 Ok(neis)
130}
131
132fn greedy_cn(graph: &Graph) -> IgraphResult<Vec<u32>> {
135 let vc = graph.vcount() as usize;
136 let mut colors = vec![0u32; vc];
137
138 if vc <= 1 {
139 return Ok(colors);
140 }
141
142 let mut max_deg = 0usize;
144 let mut start_vertex: VertexId = 0;
145 for v in 0..graph.vcount() {
146 let d = graph.degree(v)?;
147 if d > max_deg {
148 max_deg = d;
149 start_vertex = v;
150 }
151 }
152
153 let mut colored_count = vec![0u32; vc];
155 let mut colored = vec![false; vc];
156
157 let mut heap: BinaryHeap<(u32, u32)> = BinaryHeap::with_capacity(vc);
159 for v in 0..graph.vcount() {
160 if v != start_vertex {
161 heap.push((0, v));
162 }
163 }
164
165 let mut vertex = start_vertex;
167 loop {
168 let neis = all_neighbors(graph, vertex)?;
169
170 let mut nei_cols: Vec<u32> = neis.iter().map(|&n| colors[n as usize]).collect();
172 nei_cols.sort_unstable();
173 let col = smallest_available_color_1based(&nei_cols);
174 colors[vertex as usize] = col;
175 colored[vertex as usize] = true;
176
177 for &nei in &neis {
179 let ni = nei as usize;
180 if !colored[ni] {
181 colored_count[ni] = colored_count[ni].saturating_add(1);
182 heap.push((colored_count[ni], nei));
183 }
184 }
185
186 loop {
188 if let Some((count, v)) = heap.pop() {
189 if colored[v as usize] {
190 continue;
191 }
192 if count < colored_count[v as usize] {
193 continue;
194 }
195 vertex = v;
196 break;
197 }
198 for c in &mut colors {
200 *c -= 1;
201 }
202 return Ok(colors);
203 }
204 }
205}
206
207fn smallest_available_color_1based(sorted_colors: &[u32]) -> u32 {
210 let mut i = 0;
211 let mut col = 0u32;
212 let n = sorted_colors.len();
213 loop {
214 while i < n && sorted_colors[i] == col {
215 i += 1;
216 }
217 col += 1;
218 if i >= n || sorted_colors[i] != col {
219 break;
220 }
221 }
222 col
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231struct DsaturKey {
232 sat_deg: u32,
233 edge_deg: u32,
234}
235
236impl PartialOrd for DsaturKey {
237 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
238 Some(self.cmp(other))
239 }
240}
241
242impl Ord for DsaturKey {
243 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
244 self.sat_deg
245 .cmp(&other.sat_deg)
246 .then(self.edge_deg.cmp(&other.edge_deg))
247 }
248}
249
250fn greedy_dsatur(graph: &Graph) -> IgraphResult<Vec<u32>> {
253 let vc = graph.vcount() as usize;
254
255 if vc == 0 {
256 return Ok(vec![]);
257 }
258 if vc == 1 {
259 return Ok(vec![0]);
260 }
261
262 let mut colors: Vec<Option<u32>> = vec![None; vc];
263
264 let mut adj: Vec<Vec<VertexId>> = Vec::with_capacity(vc);
266 for v in 0..graph.vcount() {
267 adj.push(all_neighbors_simple(graph, v)?);
268 }
269
270 let mut sat_deg = vec![0u32; vc];
272 let mut edge_deg = vec![0u32; vc];
273 for v in 0..vc {
274 edge_deg[v] = adj[v].len() as u32;
275 }
276 let mut remaining = vc;
277
278 let mut heap: BinaryHeap<(DsaturKey, u32)> = BinaryHeap::with_capacity(vc);
280 for v in 0..graph.vcount() {
281 heap.push((
282 DsaturKey {
283 sat_deg: 0,
284 edge_deg: edge_deg[v as usize],
285 },
286 v,
287 ));
288 }
289
290 while remaining > 0 {
291 let node_to_color = loop {
293 let (key, v) = heap.pop().ok_or_else(|| {
294 IgraphError::InvalidArgument("DSATUR heap empty unexpectedly".into())
295 })?;
296 if colors[v as usize].is_some() {
297 continue;
298 }
299 let cur_key = DsaturKey {
301 sat_deg: sat_deg[v as usize],
302 edge_deg: edge_deg[v as usize],
303 };
304 if key < cur_key {
305 continue;
306 }
307 break v;
308 };
309
310 let neis = &adj[node_to_color as usize];
312 let mut used: Vec<u32> = neis.iter().filter_map(|&n| colors[n as usize]).collect();
313 used.sort_unstable();
314 let color = smallest_available_color_0based(&used);
315
316 colors[node_to_color as usize] = Some(color);
317 remaining -= 1;
318
319 for &nei in neis {
323 let ni = nei as usize;
324 if colors[ni].is_some() {
325 continue;
326 }
327
328 let color_already_used = adj[ni]
331 .iter()
332 .any(|&nn| nn != node_to_color && colors[nn as usize] == Some(color));
333
334 if !color_already_used {
335 sat_deg[ni] += 1;
336 }
337 edge_deg[ni] = edge_deg[ni].saturating_sub(1);
338
339 heap.push((
341 DsaturKey {
342 sat_deg: sat_deg[ni],
343 edge_deg: edge_deg[ni],
344 },
345 nei,
346 ));
347 }
348 }
349
350 Ok(colors.into_iter().map(|c| c.unwrap_or(0)).collect())
351}
352
353fn smallest_available_color_0based(sorted: &[u32]) -> u32 {
356 let mut col = 0u32;
357 let mut i = 0;
358 let n = sorted.len();
359 while i < n && sorted[i] == col {
360 while i < n && sorted[i] == col {
361 i += 1;
362 }
363 col += 1;
364 }
365 col
366}
367
368pub fn is_vertex_coloring(graph: &Graph, colors: &[u32]) -> IgraphResult<bool> {
390 let vc = graph.vcount() as usize;
391 if colors.len() != vc {
392 return Err(IgraphError::InvalidArgument(format!(
393 "is_vertex_coloring: colors length {} != vcount {}",
394 colors.len(),
395 vc
396 )));
397 }
398
399 let ec = graph.ecount();
400 for e in 0..ec {
401 let eid = u32::try_from(e)
402 .map_err(|_| IgraphError::InvalidArgument("edge count exceeds u32".into()))?;
403 let (from, to) = graph.edge(eid)?;
404 if from == to {
405 continue;
406 }
407 if colors[from as usize] == colors[to as usize] {
408 return Ok(false);
409 }
410 }
411
412 Ok(true)
413}
414
415pub fn is_bipartite_coloring(
443 graph: &Graph,
444 types: &[bool],
445) -> IgraphResult<BipartiteColoringResult> {
446 let vc = graph.vcount() as usize;
447 if types.len() != vc {
448 return Err(IgraphError::InvalidArgument(format!(
449 "is_bipartite_coloring: types length {} != vcount {}",
450 types.len(),
451 vc
452 )));
453 }
454
455 let ec = graph.ecount();
456 let directed = graph.is_directed();
457 let mut has_false_to_true = false;
458 let mut has_true_to_false = false;
459
460 for e in 0..ec {
461 let eid = u32::try_from(e)
462 .map_err(|_| IgraphError::InvalidArgument("edge count exceeds u32".into()))?;
463 let (from, to) = graph.edge(eid)?;
464 if from == to {
465 continue;
466 }
467
468 let from_type = types[from as usize];
469 let to_type = types[to as usize];
470
471 if from_type == to_type {
472 return Ok(BipartiteColoringResult {
473 valid: false,
474 mode: None,
475 });
476 }
477
478 if directed {
479 if !from_type && to_type {
480 has_false_to_true = true;
481 } else if from_type && !to_type {
482 has_true_to_false = true;
483 }
484 }
485 }
486
487 let mode = if directed {
488 if has_false_to_true && !has_true_to_false {
489 BipartiteEdgeDirection::Out
490 } else if !has_false_to_true && has_true_to_false {
491 BipartiteEdgeDirection::In
492 } else {
493 BipartiteEdgeDirection::All
494 }
495 } else {
496 BipartiteEdgeDirection::All
497 };
498
499 Ok(BipartiteColoringResult {
500 valid: true,
501 mode: Some(mode),
502 })
503}
504
505pub fn is_edge_coloring(graph: &Graph, colors: &[u32]) -> IgraphResult<bool> {
527 let ec = graph.ecount();
528 if colors.len() != ec {
529 return Err(IgraphError::InvalidArgument(format!(
530 "is_edge_coloring: colors length {} != ecount {}",
531 colors.len(),
532 ec
533 )));
534 }
535
536 let vc = graph.vcount();
537
538 for v in 0..vc {
539 let edges = graph.incident(v)?;
540
541 let mut seen_eids: Vec<EdgeId> = Vec::with_capacity(edges.len());
545 for &eid in &edges {
546 if !seen_eids.contains(&eid) {
547 seen_eids.push(eid);
548 }
549 }
550
551 let mut edge_cols: Vec<u32> = seen_eids.iter().map(|&eid| colors[eid as usize]).collect();
552 edge_cols.sort_unstable();
553
554 for w in edge_cols.windows(2) {
555 if w[0] == w[1] {
556 return Ok(false);
557 }
558 }
559 }
560
561 Ok(true)
562}
563
564pub fn edge_coloring_greedy(graph: &Graph) -> IgraphResult<Vec<u32>> {
592 let ec = graph.ecount();
593
594 if ec == 0 {
595 return Ok(Vec::new());
596 }
597
598 let mut colors = vec![u32::MAX; ec];
599
600 for eid in 0..ec {
603 let (u, v) = graph.edge(eid as u32)?;
604
605 let mut used = Vec::new();
607 collect_used_colors(graph, u, eid, &colors, &mut used)?;
608 if u != v {
609 collect_used_colors(graph, v, eid, &colors, &mut used)?;
610 }
611 used.sort_unstable();
612 used.dedup();
613
614 let mut color = 0u32;
616 for &c in &used {
617 if c == color {
618 color = color.checked_add(1).unwrap_or(color);
619 } else {
620 break;
621 }
622 }
623
624 colors[eid] = color;
625 }
626
627 debug_assert!(colors.iter().all(|&c| c != u32::MAX || ec == 0));
629
630 Ok(colors)
631}
632
633pub fn edge_chromatic_number(colors: &[u32]) -> u32 {
648 if colors.is_empty() {
649 return 0;
650 }
651 let max_color = colors.iter().copied().max().unwrap_or(0);
652 max_color.checked_add(1).unwrap_or(max_color)
653}
654
655fn collect_used_colors(
656 graph: &Graph,
657 v: VertexId,
658 exclude_eid: usize,
659 colors: &[u32],
660 used: &mut Vec<u32>,
661) -> IgraphResult<()> {
662 let edges = graph.incident(v)?;
663 if graph.is_directed() {
664 let in_edges = graph.incident_in(v)?;
665 for &eid in edges.iter().chain(in_edges.iter()) {
666 if (eid as usize) != exclude_eid && colors[eid as usize] != u32::MAX {
667 used.push(colors[eid as usize]);
668 }
669 }
670 } else {
671 let mut seen: Vec<EdgeId> = Vec::with_capacity(edges.len());
672 for &eid in &edges {
673 if !seen.contains(&eid) {
674 seen.push(eid);
675 if (eid as usize) != exclude_eid && colors[eid as usize] != u32::MAX {
676 used.push(colors[eid as usize]);
677 }
678 }
679 }
680 }
681 Ok(())
682}
683
684pub fn vertex_chromatic_number(colors: &[u32]) -> u32 {
709 if colors.is_empty() {
710 return 0;
711 }
712 let max_color = colors.iter().copied().max().unwrap_or(0);
713 max_color.saturating_add(1)
714}
715
716pub fn chromatic_number_upper_bound(graph: &Graph) -> IgraphResult<u32> {
747 let colors = vertex_coloring_greedy(graph, GreedyColoringHeuristic::DSatur)?;
748 Ok(vertex_chromatic_number(&colors))
749}
750
751#[cfg(test)]
756mod tests {
757 use super::*;
758 use crate::create;
759
760 fn make_undirected(n: u32, edges: &[(u32, u32)]) -> Graph {
761 create(edges, n, false).expect("make_undirected")
762 }
763
764 fn make_directed(n: u32, edges: &[(u32, u32)]) -> Graph {
765 create(edges, n, true).expect("make_directed")
766 }
767
768 #[test]
771 fn test_empty_graph_cn() {
772 let g = make_undirected(0, &[]);
773 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::ColoredNeighbors).expect("ok");
774 assert!(c.is_empty());
775 }
776
777 #[test]
778 fn test_empty_graph_dsatur() {
779 let g = make_undirected(0, &[]);
780 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
781 assert!(c.is_empty());
782 }
783
784 #[test]
785 fn test_singleton_cn() {
786 let g = make_undirected(1, &[]);
787 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::ColoredNeighbors).expect("ok");
788 assert_eq!(c, vec![0]);
789 }
790
791 #[test]
792 fn test_singleton_dsatur() {
793 let g = make_undirected(1, &[]);
794 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
795 assert_eq!(c, vec![0]);
796 }
797
798 #[test]
799 fn test_triangle_cn() {
800 let g = make_undirected(3, &[(0, 1), (1, 2), (2, 0)]);
801 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::ColoredNeighbors).expect("ok");
802 assert_eq!(c.len(), 3);
803 assert!(is_vertex_coloring(&g, &c).expect("validate"));
804 let max_color = c.iter().copied().max().unwrap_or(0);
805 assert!(max_color >= 2, "triangle needs at least 3 colors");
806 }
807
808 #[test]
809 fn test_triangle_dsatur() {
810 let g = make_undirected(3, &[(0, 1), (1, 2), (2, 0)]);
811 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
812 assert_eq!(c.len(), 3);
813 assert!(is_vertex_coloring(&g, &c).expect("validate"));
814 let num_colors = c.iter().copied().max().unwrap_or(0) + 1;
815 assert_eq!(num_colors, 3);
816 }
817
818 #[test]
819 fn test_isolated_vertices_cn() {
820 let g = make_undirected(4, &[(0, 1)]);
821 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::ColoredNeighbors).expect("ok");
822 assert!(is_vertex_coloring(&g, &c).expect("validate"));
823 assert_eq!(c[2], 0);
824 assert_eq!(c[3], 0);
825 }
826
827 #[test]
828 fn test_isolated_vertices_dsatur() {
829 let g = make_undirected(4, &[(0, 1)]);
830 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
831 assert!(is_vertex_coloring(&g, &c).expect("validate"));
832 assert_eq!(c[2], 0);
833 assert_eq!(c[3], 0);
834 }
835
836 #[test]
837 fn test_self_loops_cn() {
838 let g = make_undirected(3, &[(0, 0), (2, 2), (2, 2)]);
839 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::ColoredNeighbors).expect("ok");
840 assert!(is_vertex_coloring(&g, &c).expect("validate"));
841 let num_colors = c.iter().copied().max().unwrap_or(0) + 1;
842 assert_eq!(num_colors, 1, "only self-loops, 1 color suffices");
843 }
844
845 #[test]
846 fn test_self_loops_dsatur() {
847 let g = make_undirected(3, &[(0, 0), (2, 2), (2, 2)]);
848 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
849 assert!(is_vertex_coloring(&g, &c).expect("validate"));
850 let num_colors = c.iter().copied().max().unwrap_or(0) + 1;
851 assert_eq!(num_colors, 1);
852 }
853
854 #[test]
855 fn test_bipartite_graph_dsatur_optimal() {
856 let g = make_undirected(
857 6,
858 &[
859 (0, 3),
860 (0, 4),
861 (0, 5),
862 (1, 3),
863 (1, 4),
864 (1, 5),
865 (2, 3),
866 (2, 4),
867 (2, 5),
868 ],
869 );
870 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
871 assert!(is_vertex_coloring(&g, &c).expect("validate"));
872 let num_colors = c.iter().copied().max().unwrap_or(0) + 1;
873 assert_eq!(num_colors, 2);
874 }
875
876 #[test]
877 fn test_wheel_odd_dsatur() {
878 let mut edges: Vec<(u32, u32)> = Vec::new();
879 for i in 1..=10u32 {
880 edges.push((0, i));
881 }
882 for i in 1..10u32 {
883 edges.push((i, i + 1));
884 }
885 edges.push((10, 1));
886 let g = make_undirected(11, &edges);
887 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
888 assert!(is_vertex_coloring(&g, &c).expect("validate"));
889 let num_colors = c.iter().copied().max().unwrap_or(0) + 1;
890 assert_eq!(num_colors, 3);
891 }
892
893 #[test]
894 fn test_wheel_even_dsatur() {
895 let mut edges: Vec<(u32, u32)> = Vec::new();
896 for i in 1..=11u32 {
897 edges.push((0, i));
898 }
899 for i in 1..11u32 {
900 edges.push((i, i + 1));
901 }
902 edges.push((11, 1));
903 let g = make_undirected(12, &edges);
904 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
905 assert!(is_vertex_coloring(&g, &c).expect("validate"));
906 let num_colors = c.iter().copied().max().unwrap_or(0) + 1;
907 assert_eq!(num_colors, 4);
908 }
909
910 #[test]
911 fn test_small_graph_both_heuristics() {
912 let g = make_undirected(
913 8,
914 &[
915 (0, 3),
916 (0, 4),
917 (1, 4),
918 (2, 4),
919 (1, 5),
920 (2, 5),
921 (3, 5),
922 (0, 6),
923 (1, 6),
924 (2, 6),
925 (3, 6),
926 (0, 7),
927 (1, 7),
928 (2, 7),
929 (4, 7),
930 (5, 7),
931 ],
932 );
933
934 for heuristic in [
935 GreedyColoringHeuristic::ColoredNeighbors,
936 GreedyColoringHeuristic::DSatur,
937 ] {
938 let c = vertex_coloring_greedy(&g, heuristic).expect("ok");
939 assert_eq!(c.len(), 8);
940 assert!(is_vertex_coloring(&g, &c).expect("validate"));
941 }
942
943 let c_ds = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
944 let num_colors = c_ds.iter().copied().max().unwrap_or(0) + 1;
945 assert_eq!(num_colors, 3);
946 }
947
948 #[test]
949 fn test_multi_edges_dsatur() {
950 let g = make_undirected(
951 8,
952 &[
953 (0, 3),
954 (0, 4),
955 (1, 4),
956 (2, 4),
957 (1, 5),
958 (2, 5),
959 (3, 5),
960 (0, 6),
961 (1, 6),
962 (2, 6),
963 (3, 6),
964 (0, 7),
965 (1, 7),
966 (2, 7),
967 (4, 7),
968 (5, 7),
969 (0, 4),
970 (0, 4),
971 (3, 5),
972 (3, 5),
973 (3, 5),
974 (0, 0),
975 (0, 0),
976 (1, 1),
977 ],
978 );
979
980 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("ok");
981 assert!(is_vertex_coloring(&g, &c).expect("validate"));
982 let num_colors = c.iter().copied().max().unwrap_or(0) + 1;
983 assert_eq!(num_colors, 3);
984 }
985
986 #[test]
987 fn test_directed_graph_both() {
988 let g = make_directed(3, &[(0, 1), (1, 2), (2, 0)]);
989 for heuristic in [
990 GreedyColoringHeuristic::ColoredNeighbors,
991 GreedyColoringHeuristic::DSatur,
992 ] {
993 let c = vertex_coloring_greedy(&g, heuristic).expect("ok");
994 assert_eq!(c.len(), 3);
995 for e in 0..g.ecount() {
996 let eid = u32::try_from(e).expect("eid fits u32");
997 let (u, v) = g.edge(eid).expect("edge");
998 assert_ne!(c[u as usize], c[v as usize], "edge ({u},{v}) conflict");
999 }
1000 }
1001 }
1002
1003 #[test]
1006 fn test_is_vertex_coloring_valid() {
1007 let g = make_undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1008 assert!(is_vertex_coloring(&g, &[0, 1, 2]).expect("ok"));
1009 }
1010
1011 #[test]
1012 fn test_is_vertex_coloring_invalid() {
1013 let g = make_undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1014 assert!(!is_vertex_coloring(&g, &[0, 0, 1]).expect("ok"));
1015 }
1016
1017 #[test]
1018 fn test_is_vertex_coloring_self_loop_ok() {
1019 let g = make_undirected(2, &[(0, 0), (0, 1)]);
1020 assert!(is_vertex_coloring(&g, &[0, 1]).expect("ok"));
1021 }
1022
1023 #[test]
1024 fn test_is_vertex_coloring_wrong_size() {
1025 let g = make_undirected(3, &[(0, 1)]);
1026 assert!(is_vertex_coloring(&g, &[0, 1]).is_err());
1027 }
1028
1029 #[test]
1032 fn test_bipartite_coloring_valid_undirected() {
1033 let g = make_undirected(4, &[(0, 2), (0, 3), (1, 2), (1, 3)]);
1034 let r = is_bipartite_coloring(&g, &[false, false, true, true]).expect("ok");
1035 assert!(r.valid);
1036 assert_eq!(r.mode, Some(BipartiteEdgeDirection::All));
1037 }
1038
1039 #[test]
1040 fn test_bipartite_coloring_invalid() {
1041 let g = make_undirected(4, &[(0, 1), (0, 2), (1, 3), (2, 3)]);
1042 let r = is_bipartite_coloring(&g, &[false, false, true, true]).expect("ok");
1043 assert!(!r.valid);
1044 }
1045
1046 #[test]
1047 fn test_bipartite_coloring_directed_out() {
1048 let g = make_directed(4, &[(0, 2), (0, 3), (1, 2), (1, 3)]);
1049 let r = is_bipartite_coloring(&g, &[false, false, true, true]).expect("ok");
1050 assert!(r.valid);
1051 assert_eq!(r.mode, Some(BipartiteEdgeDirection::Out));
1052 }
1053
1054 #[test]
1055 fn test_bipartite_coloring_directed_in() {
1056 let g = make_directed(4, &[(2, 0), (3, 0), (2, 1), (3, 1)]);
1057 let r = is_bipartite_coloring(&g, &[false, false, true, true]).expect("ok");
1058 assert!(r.valid);
1059 assert_eq!(r.mode, Some(BipartiteEdgeDirection::In));
1060 }
1061
1062 #[test]
1063 fn test_bipartite_coloring_directed_all() {
1064 let g = make_directed(4, &[(0, 2), (2, 1), (1, 3), (3, 0)]);
1065 let r = is_bipartite_coloring(&g, &[false, false, true, true]).expect("ok");
1066 assert!(r.valid);
1067 assert_eq!(r.mode, Some(BipartiteEdgeDirection::All));
1068 }
1069
1070 #[test]
1071 fn test_bipartite_coloring_wrong_size() {
1072 let g = make_undirected(3, &[(0, 1)]);
1073 assert!(is_bipartite_coloring(&g, &[false, true]).is_err());
1074 }
1075
1076 #[test]
1079 fn test_edge_coloring_valid_triangle() {
1080 let g = make_undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1081 assert!(is_edge_coloring(&g, &[0, 1, 2]).expect("ok"));
1082 }
1083
1084 #[test]
1085 fn test_edge_coloring_invalid_triangle() {
1086 let g = make_undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1087 assert!(!is_edge_coloring(&g, &[0, 1, 0]).expect("ok"));
1088 }
1089
1090 #[test]
1091 fn test_edge_coloring_path() {
1092 let g = make_undirected(4, &[(0, 1), (1, 2), (2, 3)]);
1093 assert!(is_edge_coloring(&g, &[0, 1, 0]).expect("ok"));
1094 }
1095
1096 #[test]
1097 fn test_edge_coloring_wrong_size() {
1098 let g = make_undirected(2, &[(0, 1)]);
1099 assert!(is_edge_coloring(&g, &[0, 1]).is_err());
1100 }
1101
1102 #[test]
1103 fn test_edge_coloring_empty() {
1104 let g = make_undirected(0, &[]);
1105 assert!(is_edge_coloring(&g, &[]).expect("ok"));
1106 }
1107
1108 #[test]
1111 fn test_edge_coloring_greedy_empty() {
1112 let g = make_undirected(0, &[]);
1113 let colors = edge_coloring_greedy(&g).unwrap();
1114 assert!(colors.is_empty());
1115 }
1116
1117 #[test]
1118 fn test_edge_coloring_greedy_no_edges() {
1119 let g = make_undirected(5, &[]);
1120 let colors = edge_coloring_greedy(&g).unwrap();
1121 assert!(colors.is_empty());
1122 }
1123
1124 #[test]
1125 fn test_edge_coloring_greedy_single_edge() {
1126 let g = make_undirected(2, &[(0, 1)]);
1127 let colors = edge_coloring_greedy(&g).unwrap();
1128 assert_eq!(colors.len(), 1);
1129 assert_eq!(colors[0], 0);
1130 assert!(is_edge_coloring(&g, &colors).unwrap());
1131 }
1132
1133 #[test]
1134 fn test_edge_coloring_greedy_path() {
1135 let g = make_undirected(4, &[(0, 1), (1, 2), (2, 3)]);
1136 let colors = edge_coloring_greedy(&g).unwrap();
1137 assert!(is_edge_coloring(&g, &colors).unwrap());
1138 assert_eq!(edge_chromatic_number(&colors), 2); }
1140
1141 #[test]
1142 fn test_edge_coloring_greedy_triangle() {
1143 let g = make_undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1144 let colors = edge_coloring_greedy(&g).unwrap();
1145 assert!(is_edge_coloring(&g, &colors).unwrap());
1146 assert_eq!(edge_chromatic_number(&colors), 3); }
1148
1149 #[test]
1150 fn test_edge_coloring_greedy_k4() {
1151 let g = make_undirected(4, &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
1152 let colors = edge_coloring_greedy(&g).unwrap();
1153 assert!(is_edge_coloring(&g, &colors).unwrap());
1154 assert!(edge_chromatic_number(&colors) >= 3);
1156 }
1157
1158 #[test]
1159 fn test_edge_coloring_greedy_star() {
1160 let g = make_undirected(5, &[(0, 1), (0, 2), (0, 3), (0, 4)]);
1161 let colors = edge_coloring_greedy(&g).unwrap();
1162 assert!(is_edge_coloring(&g, &colors).unwrap());
1163 assert_eq!(edge_chromatic_number(&colors), 4); }
1165
1166 #[test]
1167 fn test_edge_coloring_greedy_bipartite() {
1168 let g = make_undirected(4, &[(0, 2), (0, 3), (1, 2), (1, 3)]);
1169 let colors = edge_coloring_greedy(&g).unwrap();
1170 assert!(is_edge_coloring(&g, &colors).unwrap());
1171 }
1172
1173 #[test]
1174 fn test_edge_coloring_greedy_self_loop() {
1175 let g = make_undirected(3, &[(0, 0), (0, 1), (1, 2)]);
1176 let colors = edge_coloring_greedy(&g).unwrap();
1177 assert!(is_edge_coloring(&g, &colors).unwrap());
1178 }
1179
1180 #[test]
1181 fn test_edge_coloring_greedy_parallel_edges() {
1182 let g = make_undirected(2, &[(0, 1), (0, 1), (0, 1)]);
1183 let colors = edge_coloring_greedy(&g).unwrap();
1184 assert!(is_edge_coloring(&g, &colors).unwrap());
1185 assert_eq!(edge_chromatic_number(&colors), 3);
1186 }
1187
1188 #[test]
1189 fn test_edge_coloring_greedy_directed() {
1190 let g = Graph::new(3, true).unwrap();
1191 let mut g = g;
1192 g.add_edge(0, 1).unwrap();
1193 g.add_edge(1, 2).unwrap();
1194 g.add_edge(2, 0).unwrap();
1195 let colors = edge_coloring_greedy(&g).unwrap();
1196 assert!(is_edge_coloring(&g, &colors).unwrap());
1197 }
1198
1199 #[test]
1200 fn test_edge_chromatic_number_empty() {
1201 assert_eq!(edge_chromatic_number(&[]), 0);
1202 }
1203
1204 #[test]
1205 fn test_edge_chromatic_number_values() {
1206 assert_eq!(edge_chromatic_number(&[0]), 1);
1207 assert_eq!(edge_chromatic_number(&[0, 1, 2]), 3);
1208 assert_eq!(edge_chromatic_number(&[0, 0, 0]), 1);
1209 }
1210
1211 #[test]
1214 fn test_vertex_chromatic_number_empty() {
1215 assert_eq!(vertex_chromatic_number(&[]), 0);
1216 }
1217
1218 #[test]
1219 fn test_vertex_chromatic_number_values() {
1220 assert_eq!(vertex_chromatic_number(&[0]), 1);
1221 assert_eq!(vertex_chromatic_number(&[0, 1, 2]), 3);
1222 assert_eq!(vertex_chromatic_number(&[0, 0, 0]), 1);
1223 assert_eq!(vertex_chromatic_number(&[0, 1, 0, 1]), 2);
1224 }
1225
1226 #[test]
1227 fn test_chromatic_upper_empty() {
1228 let g = make_undirected(0, &[]);
1229 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 0);
1230 }
1231
1232 #[test]
1233 fn test_chromatic_upper_singleton() {
1234 let g = make_undirected(1, &[]);
1235 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 1);
1236 }
1237
1238 #[test]
1239 fn test_chromatic_upper_no_edges() {
1240 let g = make_undirected(5, &[]);
1241 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 1);
1242 }
1243
1244 #[test]
1245 fn test_chromatic_upper_single_edge() {
1246 let g = make_undirected(2, &[(0, 1)]);
1247 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 2);
1248 }
1249
1250 #[test]
1251 fn test_chromatic_upper_triangle() {
1252 let g = make_undirected(3, &[(0, 1), (1, 2), (2, 0)]);
1253 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 3);
1254 }
1255
1256 #[test]
1257 fn test_chromatic_upper_bipartite_c4() {
1258 let g = make_undirected(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]);
1259 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 2);
1260 }
1261
1262 #[test]
1263 fn test_chromatic_upper_k4() {
1264 let g = make_undirected(4, &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
1265 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 4);
1266 }
1267
1268 #[test]
1269 fn test_chromatic_upper_path() {
1270 let g = make_undirected(4, &[(0, 1), (1, 2), (2, 3)]);
1271 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 2);
1272 }
1273
1274 #[test]
1275 fn test_chromatic_upper_star() {
1276 let g = make_undirected(5, &[(0, 1), (0, 2), (0, 3), (0, 4)]);
1277 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 2);
1278 }
1279
1280 #[test]
1281 fn test_chromatic_upper_self_loop() {
1282 let g = make_undirected(2, &[(0, 0), (0, 1)]);
1283 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 2);
1284 }
1285
1286 #[test]
1287 fn test_chromatic_upper_directed() {
1288 let g = make_directed(3, &[(0, 1), (1, 2), (2, 0)]);
1289 assert_eq!(chromatic_number_upper_bound(&g).unwrap(), 3);
1290 }
1291
1292 #[test]
1293 fn test_chromatic_upper_petersen() {
1294 let g = make_undirected(
1295 10,
1296 &[
1297 (0, 1),
1298 (1, 2),
1299 (2, 3),
1300 (3, 4),
1301 (4, 0),
1302 (5, 7),
1303 (7, 9),
1304 (9, 6),
1305 (6, 8),
1306 (8, 5),
1307 (0, 5),
1308 (1, 6),
1309 (2, 7),
1310 (3, 8),
1311 (4, 9),
1312 ],
1313 );
1314 let chi = chromatic_number_upper_bound(&g).unwrap();
1315 assert!(chi >= 3);
1316 assert!(chi <= 4);
1317 }
1318}
1319
1320#[cfg(test)]
1325#[cfg(all(test, feature = "proptest-harness"))]
1326mod proptests {
1327 use super::*;
1328 use crate::create;
1329 use proptest::prelude::*;
1330
1331 proptest! {
1332 #[test]
1333 fn prop_cn_valid_coloring(n in 2u32..50, edge_count in 1usize..100) {
1334 let max_edges = (n as usize) * (n as usize - 1) / 2;
1335 let actual_edges = edge_count.min(max_edges);
1336 let mut edges = Vec::with_capacity(actual_edges);
1337 let mut rng_state = 0x1234_5678u64;
1338 for _ in 0..actual_edges {
1339 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1340 let u = (rng_state >> 32) as u32 % n;
1341 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1342 let v = (rng_state >> 32) as u32 % n;
1343 if u != v {
1344 edges.push((u, v));
1345 }
1346 }
1347 let g = create(&edges, n, false).expect("graph");
1348 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::ColoredNeighbors)
1349 .expect("cn");
1350 prop_assert!(is_vertex_coloring(&g, &c).expect("validate"));
1351 }
1352
1353 #[test]
1354 fn prop_dsatur_valid_coloring(n in 2u32..50, edge_count in 1usize..100) {
1355 let max_edges = (n as usize) * (n as usize - 1) / 2;
1356 let actual_edges = edge_count.min(max_edges);
1357 let mut edges = Vec::with_capacity(actual_edges);
1358 let mut rng_state = 0xABCD_EF01u64;
1359 for _ in 0..actual_edges {
1360 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1361 let u = (rng_state >> 32) as u32 % n;
1362 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1363 let v = (rng_state >> 32) as u32 % n;
1364 if u != v {
1365 edges.push((u, v));
1366 }
1367 }
1368 let g = create(&edges, n, false).expect("graph");
1369 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur)
1370 .expect("dsatur");
1371 prop_assert!(is_vertex_coloring(&g, &c).expect("validate"));
1372 }
1373
1374 #[test]
1375 fn prop_dsatur_leq_cn_colors(n in 3u32..30, edge_count in 3usize..60) {
1376 let max_edges = (n as usize) * (n as usize - 1) / 2;
1377 let actual_edges = edge_count.min(max_edges);
1378 let mut edges = Vec::with_capacity(actual_edges);
1379 let mut rng_state = 0xDEAD_BEEFu64;
1380 for _ in 0..actual_edges {
1381 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1382 let u = (rng_state >> 32) as u32 % n;
1383 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1384 let v = (rng_state >> 32) as u32 % n;
1385 if u != v {
1386 edges.push((u, v));
1387 }
1388 }
1389 let g = create(&edges, n, false).expect("graph");
1390 let c_cn = vertex_coloring_greedy(&g, GreedyColoringHeuristic::ColoredNeighbors)
1391 .expect("cn");
1392 let c_ds = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur)
1393 .expect("dsatur");
1394 let n_cn = c_cn.iter().copied().max().unwrap_or(0) + 1;
1395 let n_ds = c_ds.iter().copied().max().unwrap_or(0) + 1;
1396 prop_assert!(is_vertex_coloring(&g, &c_cn).expect("validate cn"));
1397 prop_assert!(is_vertex_coloring(&g, &c_ds).expect("validate ds"));
1398 prop_assert!(n_ds <= n_cn + 2, "DSATUR used {n_ds} colors vs CN {n_cn}");
1399 }
1400
1401 #[test]
1402 fn prop_coloring_uses_contiguous_colors(n in 1u32..40, edge_count in 0usize..80) {
1403 let mut edges = Vec::new();
1404 let mut rng_state = 0xCAFE_BABEu64;
1405 for _ in 0..edge_count {
1406 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1407 let u = (rng_state >> 32) as u32 % n;
1408 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1409 let v = (rng_state >> 32) as u32 % n;
1410 if u != v {
1411 edges.push((u, v));
1412 }
1413 }
1414 let g = create(&edges, n, false).expect("graph");
1415 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("dsatur");
1416 if !c.is_empty() {
1417 let max_color = c.iter().copied().max().unwrap_or(0);
1418 let mut used = vec![false; (max_color + 1) as usize];
1419 for &col in &c {
1420 used[col as usize] = true;
1421 }
1422 for (i, &u) in used.iter().enumerate() {
1423 prop_assert!(u, "color {i} not used but max is {max_color}");
1424 }
1425 }
1426 }
1427
1428 #[test]
1429 fn prop_bipartite_coloring_after_dsatur(n1 in 1u32..15, n2 in 1u32..15) {
1430 let n = n1 + n2;
1431 let mut edges = Vec::new();
1432 for u in 0..n1 {
1433 for v in n1..n {
1434 edges.push((u, v));
1435 }
1436 }
1437 let g = create(&edges, n, false).expect("bipartite");
1438 let c = vertex_coloring_greedy(&g, GreedyColoringHeuristic::DSatur).expect("dsatur");
1439 let num_colors = c.iter().copied().max().unwrap_or(0) + 1;
1440 prop_assert_eq!(num_colors, 2, "bipartite graph should need exactly 2 colors");
1441 }
1442 }
1443}