Skip to main content

scirs2_graph/algorithms/
coloring.rs

1//! Graph coloring algorithms
2//!
3//! This module contains algorithms for graph coloring problems, including
4//! vertex coloring, edge coloring, and chromatic number estimation.
5//!
6//! # Algorithms
7//! - **Greedy coloring**: With ordering strategies (natural, largest-first, smallest-last, DSatur)
8//! - **Welsh-Powell**: Degree-ordered greedy coloring
9//! - **Chromatic number bounds**: Lower via clique, upper via greedy
10//! - **Edge coloring**: With Vizing's theorem bound
11//! - **List coloring**: Each vertex has a list of allowed colors
12
13use crate::base::{EdgeWeight, Graph, Node};
14use crate::error::{GraphError, Result};
15use petgraph::visit::EdgeRef;
16use std::collections::{HashMap, HashSet};
17
18/// Result of graph coloring
19#[derive(Debug, Clone)]
20pub struct GraphColoring<N: Node> {
21    /// The coloring as a map from node to color (0-based)
22    pub coloring: HashMap<N, usize>,
23    /// The number of colors used
24    pub num_colors: usize,
25}
26
27/// Ordering strategy for greedy coloring
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ColoringOrder {
30    /// Natural order (as nodes appear in the graph)
31    Natural,
32    /// Largest-first: order by degree descending
33    LargestFirst,
34    /// Smallest-last: iteratively remove smallest-degree vertex
35    SmallestLast,
36    /// DSatur: choose vertex with largest saturation degree
37    DSatur,
38    /// Random order (shuffled)
39    Random,
40}
41
42/// Result of edge coloring
43#[derive(Debug, Clone)]
44pub struct EdgeColoring<N: Node> {
45    /// The coloring: (source, target) -> color
46    pub coloring: HashMap<(N, N), usize>,
47    /// Number of colors used
48    pub num_colors: usize,
49    /// Maximum degree (Vizing lower bound)
50    pub max_degree: usize,
51    /// Whether the graph is class 1 (chi' = Delta) or class 2 (chi' = Delta + 1)
52    pub is_class_one: bool,
53}
54
55/// Result of chromatic number bounds
56#[derive(Debug, Clone)]
57pub struct ChromaticBounds {
58    /// Lower bound (clique number or other)
59    pub lower: usize,
60    /// Upper bound (greedy coloring)
61    pub upper: usize,
62    /// The best known coloring achieving the upper bound
63    pub best_num_colors: usize,
64}
65
66/// Result of list coloring
67#[derive(Debug, Clone)]
68pub struct ListColoring<N: Node> {
69    /// Whether a valid list coloring was found
70    pub feasible: bool,
71    /// The coloring (if feasible)
72    pub coloring: HashMap<N, usize>,
73    /// Number of colors used
74    pub num_colors: usize,
75}
76
77// ============================================================================
78// Greedy coloring with ordering strategies
79// ============================================================================
80
81/// Greedy graph coloring with configurable vertex ordering.
82///
83/// Colors vertices one at a time, always choosing the smallest available color.
84/// The vertex ordering dramatically affects the number of colors used.
85///
86/// # Arguments
87/// * `graph` - The graph to color
88///
89/// # Returns
90/// * A graph coloring using natural vertex ordering
91pub fn greedy_coloring<N, E, Ix>(graph: &Graph<N, E, Ix>) -> GraphColoring<N>
92where
93    N: Node + std::fmt::Debug,
94    E: EdgeWeight,
95    Ix: petgraph::graph::IndexType,
96{
97    greedy_coloring_with_order(graph, ColoringOrder::Natural)
98}
99
100/// Greedy coloring with a specified vertex ordering strategy.
101pub fn greedy_coloring_with_order<N, E, Ix>(
102    graph: &Graph<N, E, Ix>,
103    order: ColoringOrder,
104) -> GraphColoring<N>
105where
106    N: Node + std::fmt::Debug,
107    E: EdgeWeight,
108    Ix: petgraph::graph::IndexType,
109{
110    let node_count = graph.inner().node_count();
111    if node_count == 0 {
112        return GraphColoring {
113            coloring: HashMap::new(),
114            num_colors: 0,
115        };
116    }
117
118    let ordered_nodes = compute_ordering(graph, order);
119    color_in_order(graph, &ordered_nodes)
120}
121
122/// Compute vertex ordering based on the strategy.
123fn compute_ordering<N, E, Ix>(graph: &Graph<N, E, Ix>, order: ColoringOrder) -> Vec<N>
124where
125    N: Node + std::fmt::Debug,
126    E: EdgeWeight,
127    Ix: petgraph::graph::IndexType,
128{
129    let nodes: Vec<N> = graph
130        .inner()
131        .node_indices()
132        .map(|idx| graph.inner()[idx].clone())
133        .collect();
134
135    match order {
136        ColoringOrder::Natural => nodes,
137        ColoringOrder::LargestFirst => largest_first_order(graph, &nodes),
138        ColoringOrder::SmallestLast => smallest_last_order(graph, &nodes),
139        ColoringOrder::DSatur => {
140            // DSatur is handled differently - return natural order
141            // and use dsatur_coloring directly
142            nodes
143        }
144        ColoringOrder::Random => {
145            let mut shuffled = nodes;
146            // Simple deterministic shuffle using indices
147            let n = shuffled.len();
148            for i in 0..n {
149                let j = (i * 7 + 3) % n;
150                shuffled.swap(i, j);
151            }
152            shuffled
153        }
154    }
155}
156
157/// Largest-first ordering: sort by degree descending.
158fn largest_first_order<N, E, Ix>(graph: &Graph<N, E, Ix>, nodes: &[N]) -> Vec<N>
159where
160    N: Node + std::fmt::Debug,
161    E: EdgeWeight,
162    Ix: petgraph::graph::IndexType,
163{
164    let mut node_degrees: Vec<(N, usize)> =
165        nodes.iter().map(|n| (n.clone(), graph.degree(n))).collect();
166    node_degrees.sort_by(|a, b| b.1.cmp(&a.1));
167    node_degrees.into_iter().map(|(n, _)| n).collect()
168}
169
170/// Smallest-last ordering: iteratively remove smallest-degree vertex.
171///
172/// This produces orderings that tend to use fewer colors, especially
173/// for sparse graphs.
174fn smallest_last_order<N, E, Ix>(graph: &Graph<N, E, Ix>, nodes: &[N]) -> Vec<N>
175where
176    N: Node + std::fmt::Debug,
177    E: EdgeWeight,
178    Ix: petgraph::graph::IndexType,
179{
180    let n = nodes.len();
181    let mut remaining: HashSet<N> = nodes.iter().cloned().collect();
182    let mut order = Vec::with_capacity(n);
183
184    // Build adjacency map
185    let mut adj: HashMap<N, HashSet<N>> = HashMap::new();
186    for node in nodes {
187        if let Ok(neighbors) = graph.neighbors(node) {
188            let neighbor_set: HashSet<N> = neighbors.into_iter().collect();
189            adj.insert(node.clone(), neighbor_set);
190        } else {
191            adj.insert(node.clone(), HashSet::new());
192        }
193    }
194
195    for _ in 0..n {
196        // Find node with smallest degree among remaining nodes
197        let mut min_deg = usize::MAX;
198        let mut min_node = None;
199
200        for node in &remaining {
201            let deg = adj
202                .get(node)
203                .map(|neighbors| neighbors.iter().filter(|n| remaining.contains(n)).count())
204                .unwrap_or(0);
205            if deg < min_deg {
206                min_deg = deg;
207                min_node = Some(node.clone());
208            }
209        }
210
211        if let Some(node) = min_node {
212            order.push(node.clone());
213            remaining.remove(&node);
214        }
215    }
216
217    // Reverse: smallest-last means we color in reverse removal order
218    order.reverse();
219    order
220}
221
222/// Color nodes in the given order using greedy assignment.
223fn color_in_order<N, E, Ix>(graph: &Graph<N, E, Ix>, ordered_nodes: &[N]) -> GraphColoring<N>
224where
225    N: Node + std::fmt::Debug,
226    E: EdgeWeight,
227    Ix: petgraph::graph::IndexType,
228{
229    let mut coloring: HashMap<N, usize> = HashMap::new();
230    let mut max_color = 0;
231
232    for node in ordered_nodes {
233        // Find colors used by already-colored neighbors
234        let mut used_colors = HashSet::new();
235        if let Ok(neighbors) = graph.neighbors(node) {
236            for neighbor in &neighbors {
237                if let Some(&color) = coloring.get(neighbor) {
238                    used_colors.insert(color);
239                }
240            }
241        }
242
243        // Find smallest available color
244        let mut color = 0;
245        while used_colors.contains(&color) {
246            color += 1;
247        }
248
249        coloring.insert(node.clone(), color);
250        if color >= max_color {
251            max_color = color + 1;
252        }
253    }
254
255    GraphColoring {
256        coloring,
257        num_colors: max_color,
258    }
259}
260
261// ============================================================================
262// DSatur coloring
263// ============================================================================
264
265/// DSatur (Degree of Saturation) coloring algorithm.
266///
267/// At each step, colors the vertex with the highest saturation degree
268/// (number of distinct colors among its colored neighbors). Ties are
269/// broken by choosing the vertex with highest uncolored degree.
270///
271/// This generally produces better colorings than basic greedy.
272pub fn dsatur_coloring<N, E, Ix>(graph: &Graph<N, E, Ix>) -> GraphColoring<N>
273where
274    N: Node + std::fmt::Debug,
275    E: EdgeWeight,
276    Ix: petgraph::graph::IndexType,
277{
278    let nodes: Vec<N> = graph
279        .inner()
280        .node_indices()
281        .map(|idx| graph.inner()[idx].clone())
282        .collect();
283
284    let n = nodes.len();
285    if n == 0 {
286        return GraphColoring {
287            coloring: HashMap::new(),
288            num_colors: 0,
289        };
290    }
291
292    let mut coloring: HashMap<N, usize> = HashMap::new();
293    let mut saturation: HashMap<N, HashSet<usize>> = HashMap::new();
294    let mut colored = HashSet::new();
295    let mut max_color = 0;
296
297    for node in &nodes {
298        saturation.insert(node.clone(), HashSet::new());
299    }
300
301    for _ in 0..n {
302        // Find uncolored vertex with highest saturation degree
303        let mut best_node = None;
304        let mut best_sat = 0;
305        let mut best_deg = 0;
306
307        for node in &nodes {
308            if colored.contains(node) {
309                continue;
310            }
311
312            let sat = saturation.get(node).map(|s| s.len()).unwrap_or(0);
313            let deg = graph.degree(node);
314
315            if best_node.is_none() || sat > best_sat || (sat == best_sat && deg > best_deg) {
316                best_node = Some(node.clone());
317                best_sat = sat;
318                best_deg = deg;
319            }
320        }
321
322        if let Some(node) = best_node {
323            // Find used colors among neighbors
324            let mut used_colors = HashSet::new();
325            if let Ok(neighbors) = graph.neighbors(&node) {
326                for neighbor in &neighbors {
327                    if let Some(&color) = coloring.get(neighbor) {
328                        used_colors.insert(color);
329                    }
330                }
331            }
332
333            // Pick smallest available color
334            let mut color = 0;
335            while used_colors.contains(&color) {
336                color += 1;
337            }
338
339            coloring.insert(node.clone(), color);
340            colored.insert(node.clone());
341            if color + 1 > max_color {
342                max_color = color + 1;
343            }
344
345            // Update saturation of uncolored neighbors
346            if let Ok(neighbors) = graph.neighbors(&node) {
347                for neighbor in &neighbors {
348                    if !colored.contains(neighbor) {
349                        if let Some(sat) = saturation.get_mut(neighbor) {
350                            sat.insert(color);
351                        }
352                    }
353                }
354            }
355        }
356    }
357
358    GraphColoring {
359        coloring,
360        num_colors: max_color,
361    }
362}
363
364// ============================================================================
365// Welsh-Powell Algorithm
366// ============================================================================
367
368/// Welsh-Powell coloring algorithm.
369///
370/// Orders vertices by decreasing degree, then applies greedy coloring.
371/// This is equivalent to `greedy_coloring_with_order(graph, ColoringOrder::LargestFirst)`.
372pub fn welsh_powell<N, E, Ix>(graph: &Graph<N, E, Ix>) -> GraphColoring<N>
373where
374    N: Node + std::fmt::Debug,
375    E: EdgeWeight,
376    Ix: petgraph::graph::IndexType,
377{
378    greedy_coloring_with_order(graph, ColoringOrder::LargestFirst)
379}
380
381// ============================================================================
382// Chromatic Number Bounds
383// ============================================================================
384
385/// Finds the chromatic number of a graph (minimum number of colors needed).
386///
387/// Uses an exhaustive search up to `max_colors` to find the minimum coloring.
388/// This is an NP-complete problem, so it may be slow for large graphs.
389pub fn chromatic_number<N, E, Ix>(graph: &Graph<N, E, Ix>, max_colors: usize) -> Option<usize>
390where
391    N: Node + std::fmt::Debug,
392    E: EdgeWeight,
393    Ix: petgraph::graph::IndexType,
394{
395    if graph.inner().node_count() == 0 {
396        return Some(0);
397    }
398
399    (1..=max_colors).find(|&num_colors| can_color_with_k_colors(graph, num_colors))
400}
401
402/// Check if a graph can be colored with k colors (backtracking).
403fn can_color_with_k_colors<N, E, Ix>(graph: &Graph<N, E, Ix>, k: usize) -> bool
404where
405    N: Node + std::fmt::Debug,
406    E: EdgeWeight,
407    Ix: petgraph::graph::IndexType,
408{
409    let nodes: Vec<_> = graph.inner().node_indices().collect();
410    let mut coloring = vec![0usize; nodes.len()];
411
412    fn backtrack<N, E, Ix>(
413        graph: &Graph<N, E, Ix>,
414        nodes: &[petgraph::graph::NodeIndex<Ix>],
415        coloring: &mut [usize],
416        node_idx: usize,
417        k: usize,
418    ) -> bool
419    where
420        N: Node + std::fmt::Debug,
421        E: EdgeWeight,
422        Ix: petgraph::graph::IndexType,
423    {
424        if node_idx == nodes.len() {
425            return true;
426        }
427
428        let node = nodes[node_idx];
429
430        for color in 0..k {
431            let mut valid = true;
432            for (i, &other_node) in nodes.iter().enumerate().take(node_idx) {
433                if (graph.inner().contains_edge(node, other_node)
434                    || graph.inner().contains_edge(other_node, node))
435                    && coloring[i] == color
436                {
437                    valid = false;
438                    break;
439                }
440            }
441
442            if valid {
443                coloring[node_idx] = color;
444                if backtrack(graph, nodes, coloring, node_idx + 1, k) {
445                    return true;
446                }
447            }
448        }
449
450        false
451    }
452
453    backtrack(graph, &nodes, &mut coloring, 0, k)
454}
455
456/// Compute chromatic number bounds using clique (lower) and greedy (upper).
457///
458/// The lower bound is the clique number (found via greedy clique search).
459/// The upper bound is from the best greedy coloring (DSatur).
460pub fn chromatic_bounds<N, E, Ix>(graph: &Graph<N, E, Ix>) -> ChromaticBounds
461where
462    N: Node + std::fmt::Debug,
463    E: EdgeWeight,
464    Ix: petgraph::graph::IndexType,
465{
466    let n = graph.inner().node_count();
467    if n == 0 {
468        return ChromaticBounds {
469            lower: 0,
470            upper: 0,
471            best_num_colors: 0,
472        };
473    }
474
475    // Lower bound: greedy clique finding
476    let lower = greedy_clique_number(graph);
477
478    // Upper bound: best of multiple orderings
479    let dsatur_result = dsatur_coloring(graph);
480    let welsh_result = welsh_powell(graph);
481    let sl_result = greedy_coloring_with_order(graph, ColoringOrder::SmallestLast);
482
483    let upper = dsatur_result
484        .num_colors
485        .min(welsh_result.num_colors)
486        .min(sl_result.num_colors);
487
488    // Also: upper <= max_degree + 1 (Brook's theorem, except complete graphs and odd cycles)
489    let max_degree = graph
490        .inner()
491        .node_indices()
492        .map(|idx| graph.inner().neighbors(idx).count())
493        .max()
494        .unwrap_or(0);
495    let brooks_upper = max_degree + 1;
496
497    let final_upper = upper.min(brooks_upper);
498
499    ChromaticBounds {
500        lower,
501        upper: final_upper,
502        best_num_colors: final_upper,
503    }
504}
505
506/// Find a large clique greedily to bound chromatic number from below.
507fn greedy_clique_number<N, E, Ix>(graph: &Graph<N, E, Ix>) -> usize
508where
509    N: Node + std::fmt::Debug,
510    E: EdgeWeight,
511    Ix: petgraph::graph::IndexType,
512{
513    let nodes: Vec<N> = graph
514        .inner()
515        .node_indices()
516        .map(|idx| graph.inner()[idx].clone())
517        .collect();
518
519    if nodes.is_empty() {
520        return 0;
521    }
522
523    let mut max_clique_size = 1;
524
525    // Try starting from each node (greedy clique extension)
526    for start_node in &nodes {
527        let mut clique = vec![start_node.clone()];
528
529        // Try to extend the clique
530        // Sort candidates by degree descending for better cliques
531        let mut candidates: Vec<N> = Vec::new();
532        if let Ok(neighbors) = graph.neighbors(start_node) {
533            candidates = neighbors;
534        }
535        candidates.sort_by_key(|b| std::cmp::Reverse(graph.degree(b)));
536
537        for candidate in &candidates {
538            // Check if candidate is adjacent to all nodes in the clique
539            let all_adjacent = clique.iter().all(|c| graph.has_edge(candidate, c));
540            if all_adjacent {
541                clique.push(candidate.clone());
542            }
543        }
544
545        max_clique_size = max_clique_size.max(clique.len());
546    }
547
548    max_clique_size
549}
550
551// ============================================================================
552// Edge Coloring
553// ============================================================================
554
555/// Edge coloring using greedy approach with Vizing's theorem bound.
556///
557/// Vizing's theorem states that every simple graph can be edge-colored
558/// with at most Delta + 1 colors, where Delta is the maximum degree.
559/// A graph is class 1 if Delta colors suffice, class 2 if Delta + 1 are needed.
560pub fn edge_coloring<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<EdgeColoring<N>>
561where
562    N: Node + Clone + std::fmt::Debug,
563    E: EdgeWeight + Clone,
564    Ix: petgraph::graph::IndexType,
565{
566    let edges = graph.edges();
567    let n = graph.inner().node_count();
568
569    if n == 0 || edges.is_empty() {
570        return Ok(EdgeColoring {
571            coloring: HashMap::new(),
572            num_colors: 0,
573            max_degree: 0,
574            is_class_one: true,
575        });
576    }
577
578    let max_degree = graph
579        .inner()
580        .node_indices()
581        .map(|idx| graph.inner().neighbors(idx).count())
582        .max()
583        .unwrap_or(0);
584
585    // Greedy edge coloring: color each edge with the smallest color
586    // not used by any adjacent edge (edge sharing a vertex)
587    let mut edge_colors: HashMap<(N, N), usize> = HashMap::new();
588    let mut node_used_colors: HashMap<N, HashSet<usize>> = HashMap::new();
589    let mut num_colors = 0;
590
591    for edge in &edges {
592        let src = edge.source.clone();
593        let tgt = edge.target.clone();
594
595        // Colors used at source
596        let src_colors = node_used_colors.get(&src).cloned().unwrap_or_default();
597        // Colors used at target
598        let tgt_colors = node_used_colors.get(&tgt).cloned().unwrap_or_default();
599
600        // Find smallest unused color at both endpoints
601        let mut color = 0;
602        while src_colors.contains(&color) || tgt_colors.contains(&color) {
603            color += 1;
604        }
605
606        edge_colors.insert((src.clone(), tgt.clone()), color);
607
608        node_used_colors.entry(src).or_default().insert(color);
609        node_used_colors.entry(tgt).or_default().insert(color);
610
611        if color + 1 > num_colors {
612            num_colors = color + 1;
613        }
614    }
615
616    let is_class_one = num_colors <= max_degree;
617
618    Ok(EdgeColoring {
619        coloring: edge_colors,
620        num_colors,
621        max_degree,
622        is_class_one,
623    })
624}
625
626// ============================================================================
627// List Coloring
628// ============================================================================
629
630/// List coloring: each vertex has a set of allowed colors.
631///
632/// Attempts to find a proper coloring where each vertex is assigned
633/// a color from its allowed list. Uses backtracking.
634///
635/// # Arguments
636/// * `graph` - The graph to color
637/// * `color_lists` - Map from node to set of allowed colors
638pub fn list_coloring<N, E, Ix>(
639    graph: &Graph<N, E, Ix>,
640    color_lists: &HashMap<N, Vec<usize>>,
641) -> Result<ListColoring<N>>
642where
643    N: Node + Clone + std::fmt::Debug,
644    E: EdgeWeight,
645    Ix: petgraph::graph::IndexType,
646{
647    let nodes: Vec<N> = graph
648        .inner()
649        .node_indices()
650        .map(|idx| graph.inner()[idx].clone())
651        .collect();
652
653    if nodes.is_empty() {
654        return Ok(ListColoring {
655            feasible: true,
656            coloring: HashMap::new(),
657            num_colors: 0,
658        });
659    }
660
661    // Verify all nodes have color lists
662    for node in &nodes {
663        if !color_lists.contains_key(node) {
664            return Err(GraphError::InvalidGraph(format!(
665                "Node {node:?} has no color list"
666            )));
667        }
668    }
669
670    let mut coloring: HashMap<N, usize> = HashMap::new();
671    let feasible = list_coloring_backtrack(graph, &nodes, color_lists, &mut coloring, 0);
672
673    if feasible {
674        let num_colors = coloring.values().copied().collect::<HashSet<_>>().len();
675        Ok(ListColoring {
676            feasible: true,
677            coloring,
678            num_colors,
679        })
680    } else {
681        Ok(ListColoring {
682            feasible: false,
683            coloring: HashMap::new(),
684            num_colors: 0,
685        })
686    }
687}
688
689/// Backtracking for list coloring.
690fn list_coloring_backtrack<N, E, Ix>(
691    graph: &Graph<N, E, Ix>,
692    nodes: &[N],
693    color_lists: &HashMap<N, Vec<usize>>,
694    coloring: &mut HashMap<N, usize>,
695    idx: usize,
696) -> bool
697where
698    N: Node + Clone + std::fmt::Debug,
699    E: EdgeWeight,
700    Ix: petgraph::graph::IndexType,
701{
702    if idx == nodes.len() {
703        return true;
704    }
705
706    let node = &nodes[idx];
707    let allowed = color_lists.get(node).cloned().unwrap_or_default();
708
709    // Get colors used by already-colored neighbors
710    let mut forbidden = HashSet::new();
711    if let Ok(neighbors) = graph.neighbors(node) {
712        for neighbor in &neighbors {
713            if let Some(&c) = coloring.get(neighbor) {
714                forbidden.insert(c);
715            }
716        }
717    }
718
719    for &color in &allowed {
720        if !forbidden.contains(&color) {
721            coloring.insert(node.clone(), color);
722            if list_coloring_backtrack(graph, nodes, color_lists, coloring, idx + 1) {
723                return true;
724            }
725            coloring.remove(node);
726        }
727    }
728
729    false
730}
731
732// ============================================================================
733// Utility: verify a coloring is valid
734// ============================================================================
735
736/// Verify that a graph coloring is valid (no two adjacent nodes share a color).
737pub fn verify_coloring<N, E, Ix>(graph: &Graph<N, E, Ix>, coloring: &HashMap<N, usize>) -> bool
738where
739    N: Node + std::fmt::Debug,
740    E: EdgeWeight,
741    Ix: petgraph::graph::IndexType,
742{
743    for edge in graph.inner().edge_references() {
744        let src = &graph.inner()[edge.source()];
745        let tgt = &graph.inner()[edge.target()];
746
747        if let (Some(&c1), Some(&c2)) = (coloring.get(src), coloring.get(tgt)) {
748            if c1 == c2 {
749                return false;
750            }
751        }
752    }
753    true
754}
755
756// ============================================================================
757// Tests
758// ============================================================================
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763    use crate::generators::create_graph;
764    use petgraph::visit::EdgeRef;
765
766    #[test]
767    fn test_greedy_coloring() {
768        let mut graph = create_graph::<char, ()>();
769        graph.add_edge('A', 'B', ()).expect("add edge");
770        graph.add_edge('B', 'C', ()).expect("add edge");
771        graph.add_edge('C', 'A', ()).expect("add edge");
772
773        let coloring = greedy_coloring(&graph);
774        assert_eq!(coloring.num_colors, 3);
775        assert_ne!(coloring.coloring[&'A'], coloring.coloring[&'B']);
776        assert_ne!(coloring.coloring[&'B'], coloring.coloring[&'C']);
777        assert_ne!(coloring.coloring[&'C'], coloring.coloring[&'A']);
778    }
779
780    #[test]
781    fn test_bipartite_graph_coloring() {
782        let mut graph = create_graph::<i32, ()>();
783        graph.add_edge(0, 1, ()).expect("add edge");
784        graph.add_edge(0, 3, ()).expect("add edge");
785        graph.add_edge(2, 1, ()).expect("add edge");
786        graph.add_edge(2, 3, ()).expect("add edge");
787
788        let coloring = greedy_coloring(&graph);
789        assert!(coloring.num_colors <= 2);
790    }
791
792    #[test]
793    fn test_chromatic_number() {
794        let mut triangle = create_graph::<i32, ()>();
795        triangle.add_edge(0, 1, ()).expect("add edge");
796        triangle.add_edge(1, 2, ()).expect("add edge");
797        triangle.add_edge(2, 0, ()).expect("add edge");
798
799        assert_eq!(chromatic_number(&triangle, 5), Some(3));
800
801        let mut bipartite = create_graph::<i32, ()>();
802        bipartite.add_edge(0, 1, ()).expect("add edge");
803        bipartite.add_edge(1, 2, ()).expect("add edge");
804        bipartite.add_edge(2, 3, ()).expect("add edge");
805        bipartite.add_edge(3, 0, ()).expect("add edge");
806
807        assert_eq!(chromatic_number(&bipartite, 5), Some(2));
808
809        let empty = create_graph::<i32, ()>();
810        assert_eq!(chromatic_number(&empty, 5), Some(0));
811    }
812
813    #[test]
814    fn test_largest_first_coloring() {
815        let mut graph = create_graph::<i32, ()>();
816        graph.add_edge(0, 1, ()).expect("add edge");
817        graph.add_edge(0, 2, ()).expect("add edge");
818        graph.add_edge(0, 3, ()).expect("add edge");
819        graph.add_edge(1, 2, ()).expect("add edge");
820
821        let coloring = greedy_coloring_with_order(&graph, ColoringOrder::LargestFirst);
822        assert!(verify_coloring(&graph, &coloring.coloring));
823    }
824
825    #[test]
826    fn test_smallest_last_coloring() {
827        let mut graph = create_graph::<i32, ()>();
828        for i in 0..5 {
829            for j in (i + 1)..5 {
830                graph.add_edge(i, j, ()).expect("add edge");
831            }
832        }
833
834        let coloring = greedy_coloring_with_order(&graph, ColoringOrder::SmallestLast);
835        assert!(verify_coloring(&graph, &coloring.coloring));
836        assert_eq!(coloring.num_colors, 5); // Complete graph K5 needs 5 colors
837    }
838
839    #[test]
840    fn test_dsatur_coloring() {
841        let mut graph = create_graph::<i32, ()>();
842        graph.add_edge(0, 1, ()).expect("add edge");
843        graph.add_edge(1, 2, ()).expect("add edge");
844        graph.add_edge(2, 0, ()).expect("add edge");
845        graph.add_edge(2, 3, ()).expect("add edge");
846        graph.add_edge(3, 4, ()).expect("add edge");
847
848        let coloring = dsatur_coloring(&graph);
849        assert!(verify_coloring(&graph, &coloring.coloring));
850        // Triangle needs 3, rest might need fewer
851        assert!(coloring.num_colors >= 3);
852    }
853
854    #[test]
855    fn test_welsh_powell() {
856        let mut graph = create_graph::<i32, ()>();
857        // Crown graph (bipartite)
858        graph.add_edge(0, 3, ()).expect("add edge");
859        graph.add_edge(0, 4, ()).expect("add edge");
860        graph.add_edge(1, 3, ()).expect("add edge");
861        graph.add_edge(1, 5, ()).expect("add edge");
862        graph.add_edge(2, 4, ()).expect("add edge");
863        graph.add_edge(2, 5, ()).expect("add edge");
864
865        let coloring = welsh_powell(&graph);
866        assert!(verify_coloring(&graph, &coloring.coloring));
867        assert!(coloring.num_colors <= 2);
868    }
869
870    #[test]
871    fn test_chromatic_bounds() {
872        let mut graph = create_graph::<i32, ()>();
873        // Petersen graph is 3-chromatic
874        // But let's use a simpler example: triangle
875        graph.add_edge(0, 1, ()).expect("add edge");
876        graph.add_edge(1, 2, ()).expect("add edge");
877        graph.add_edge(2, 0, ()).expect("add edge");
878
879        let bounds = chromatic_bounds(&graph);
880        assert!(bounds.lower >= 3); // Triangle is a 3-clique
881        assert!(bounds.upper >= 3);
882        assert!(bounds.lower <= bounds.upper);
883    }
884
885    #[test]
886    fn test_edge_coloring() -> Result<()> {
887        let mut graph = create_graph::<i32, ()>();
888        graph.add_edge(0, 1, ())?;
889        graph.add_edge(1, 2, ())?;
890        graph.add_edge(2, 0, ())?;
891
892        let result = edge_coloring(&graph)?;
893        assert_eq!(result.max_degree, 2);
894        // Triangle needs 3 edge colors (class 2)
895        assert!(result.num_colors <= result.max_degree + 1);
896
897        // Verify edge coloring: no two edges sharing a vertex have same color
898        for edge1 in graph.inner().edge_references() {
899            for edge2 in graph.inner().edge_references() {
900                if edge1.id() == edge2.id() {
901                    continue;
902                }
903                if edge1.source() == edge2.source()
904                    || edge1.source() == edge2.target()
905                    || edge1.target() == edge2.source()
906                    || edge1.target() == edge2.target()
907                {
908                    let s1 = graph.inner()[edge1.source()].clone();
909                    let t1 = graph.inner()[edge1.target()].clone();
910                    let s2 = graph.inner()[edge2.source()].clone();
911                    let t2 = graph.inner()[edge2.target()].clone();
912
913                    let c1 = result
914                        .coloring
915                        .get(&(s1.clone(), t1.clone()))
916                        .or_else(|| result.coloring.get(&(t1, s1)));
917                    let c2 = result
918                        .coloring
919                        .get(&(s2.clone(), t2.clone()))
920                        .or_else(|| result.coloring.get(&(t2, s2)));
921
922                    if let (Some(c1), Some(c2)) = (c1, c2) {
923                        assert_ne!(c1, c2, "Adjacent edges should have different colors");
924                    }
925                }
926            }
927        }
928        Ok(())
929    }
930
931    #[test]
932    fn test_edge_coloring_bipartite() -> Result<()> {
933        // Bipartite graph: class 1 (edge chromatic number = max degree)
934        let mut graph = create_graph::<i32, ()>();
935        graph.add_edge(0, 2, ())?;
936        graph.add_edge(0, 3, ())?;
937        graph.add_edge(1, 2, ())?;
938        graph.add_edge(1, 3, ())?;
939
940        let result = edge_coloring(&graph)?;
941        assert_eq!(result.max_degree, 2);
942        assert!(result.num_colors <= 3); // At most Delta + 1
943        Ok(())
944    }
945
946    #[test]
947    fn test_list_coloring_feasible() -> Result<()> {
948        let mut graph = create_graph::<i32, ()>();
949        graph.add_edge(0, 1, ())?;
950        graph.add_edge(1, 2, ())?;
951
952        let mut lists = HashMap::new();
953        lists.insert(0, vec![0, 1]);
954        lists.insert(1, vec![0, 1]);
955        lists.insert(2, vec![0, 1]);
956
957        let result = list_coloring(&graph, &lists)?;
958        assert!(result.feasible);
959        assert!(result.num_colors <= 2);
960        Ok(())
961    }
962
963    #[test]
964    fn test_list_coloring_infeasible() -> Result<()> {
965        // Triangle with too-restrictive lists
966        let mut graph = create_graph::<i32, ()>();
967        graph.add_edge(0, 1, ())?;
968        graph.add_edge(1, 2, ())?;
969        graph.add_edge(2, 0, ())?;
970
971        let mut lists = HashMap::new();
972        lists.insert(0, vec![0, 1]);
973        lists.insert(1, vec![0, 1]);
974        lists.insert(2, vec![0, 1]); // Triangle can't be 2-colored
975
976        let result = list_coloring(&graph, &lists)?;
977        assert!(!result.feasible);
978        Ok(())
979    }
980
981    #[test]
982    fn test_list_coloring_with_enough_colors() -> Result<()> {
983        let mut graph = create_graph::<i32, ()>();
984        graph.add_edge(0, 1, ())?;
985        graph.add_edge(1, 2, ())?;
986        graph.add_edge(2, 0, ())?;
987
988        let mut lists = HashMap::new();
989        lists.insert(0, vec![0, 1, 2]);
990        lists.insert(1, vec![0, 1, 2]);
991        lists.insert(2, vec![0, 1, 2]);
992
993        let result = list_coloring(&graph, &lists)?;
994        assert!(result.feasible);
995        assert_eq!(result.num_colors, 3);
996        Ok(())
997    }
998
999    #[test]
1000    fn test_verify_coloring_valid() {
1001        let mut graph = create_graph::<i32, ()>();
1002        graph.add_edge(0, 1, ()).expect("add edge");
1003        graph.add_edge(1, 2, ()).expect("add edge");
1004
1005        let mut coloring = HashMap::new();
1006        coloring.insert(0, 0);
1007        coloring.insert(1, 1);
1008        coloring.insert(2, 0);
1009
1010        assert!(verify_coloring(&graph, &coloring));
1011    }
1012
1013    #[test]
1014    fn test_verify_coloring_invalid() {
1015        let mut graph = create_graph::<i32, ()>();
1016        graph.add_edge(0, 1, ()).expect("add edge");
1017
1018        let mut coloring = HashMap::new();
1019        coloring.insert(0, 0);
1020        coloring.insert(1, 0); // Same color as neighbor
1021
1022        assert!(!verify_coloring(&graph, &coloring));
1023    }
1024
1025    #[test]
1026    fn test_empty_graph_coloring() {
1027        let graph = create_graph::<i32, ()>();
1028        let coloring = greedy_coloring(&graph);
1029        assert_eq!(coloring.num_colors, 0);
1030        assert!(coloring.coloring.is_empty());
1031    }
1032
1033    #[test]
1034    fn test_single_node() {
1035        let mut graph = create_graph::<i32, ()>();
1036        let _ = graph.add_node(42);
1037        let coloring = greedy_coloring(&graph);
1038        assert_eq!(coloring.num_colors, 1);
1039        assert_eq!(coloring.coloring[&42], 0);
1040    }
1041}