use selen::prelude::*;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Graph {
pub vertices: Vec<usize>,
pub edges: Vec<(usize, usize)>,
pub name: String,
}
impl Graph {
pub fn new(name: &str, vertices: Vec<usize>, edges: Vec<(usize, usize)>) -> Self {
Self {
vertices,
edges,
name: name.to_string(),
}
}
pub fn complete(n: usize) -> Self {
let vertices = (0..n).collect();
let mut edges = Vec::new();
for i in 0..n {
for j in (i + 1)..n {
edges.push((i, j));
}
}
Self::new(&format!("K_{}", n), vertices, edges)
}
pub fn cycle(n: usize) -> Self {
let vertices = (0..n).collect();
let mut edges = Vec::new();
for i in 0..n {
edges.push((i, (i + 1) % n));
}
Self::new(&format!("C_{}", n), vertices, edges)
}
pub fn bipartite(m: usize, n: usize) -> Self {
let vertices = (0..(m + n)).collect();
let mut edges = Vec::new();
for i in 0..m {
for j in m..(m + n) {
edges.push((i, j));
}
}
Self::new(&format!("K_{},{}", m, n), vertices, edges)
}
pub fn petersen() -> Self {
let vertices = (0..10).collect();
let edges = vec![
(0, 1), (1, 2), (2, 3), (3, 4), (4, 0),
(5, 7), (7, 9), (9, 6), (6, 8), (8, 5),
(0, 5), (1, 6), (2, 7), (3, 8), (4, 9),
];
Self::new("Petersen", vertices, edges)
}
pub fn planar_4_colorable() -> Self {
let vertices = (0..8).collect();
let edges = vec![
(0, 1), (0, 2), (0, 3),
(1, 2), (1, 4), (1, 5),
(2, 3), (2, 5), (2, 6),
(3, 6), (3, 7),
(4, 5), (4, 7),
(5, 6), (5, 7),
(6, 7),
];
Self::new("Planar4Color", vertices, edges)
}
pub fn get_neighbors(&self, vertex: usize) -> Vec<usize> {
let mut neighbors = Vec::new();
for &(u, v) in &self.edges {
if u == vertex {
neighbors.push(v);
} else if v == vertex {
neighbors.push(u);
}
}
neighbors
}
}
pub fn solve_graph_coloring(graph: &Graph, num_colors: usize) -> Result<Solution, String> {
let mut model = Model::default();
let mut color_vars = HashMap::new();
for &vertex in &graph.vertices {
let var = model.int(0, num_colors as i32 - 1);
color_vars.insert(vertex, var);
}
for &(u, v) in &graph.edges {
let var_u = color_vars[&u];
let var_v = color_vars[&v];
model.new(var_u.ne(var_v));
}
match model.solve() {
Ok(solution) => {
println!("✅ Successfully colored {} with {} colors!", graph.name, num_colors);
for &vertex in &graph.vertices {
if let Val::ValI(color) = solution[color_vars[&vertex]] {
println!(" Vertex {}: Color {}", vertex, color);
}
}
verify_coloring(&graph, &solution, &color_vars)?;
Ok(solution)
},
Err(_) => {
Err(format!("❌ Cannot color {} with {} colors", graph.name, num_colors))
}
}
}
pub fn find_chromatic_number(graph: &Graph) -> usize {
println!("🔍 Finding chromatic number for {}...", graph.name);
let mut low = 1;
let mut high = graph.vertices.len();
let mut chromatic_number = high;
while low <= high {
let mid = (low + high) / 2;
match solve_graph_coloring(graph, mid) {
Ok(_) => {
chromatic_number = mid;
high = mid - 1;
println!(" ✓ {} colors work", mid);
},
Err(_) => {
low = mid + 1;
println!(" ✗ {} colors insufficient", mid);
}
}
}
println!("🎯 Chromatic number of {}: {}", graph.name, chromatic_number);
chromatic_number
}
fn verify_coloring(
graph: &Graph,
solution: &Solution,
color_vars: &HashMap<usize, VarId>
) -> Result<(), String> {
for &(u, v) in &graph.edges {
let color_u = if let Val::ValI(val) = solution[color_vars[&u]] { val } else { return Err("Invalid solution".to_string()); };
let color_v = if let Val::ValI(val) = solution[color_vars[&v]] { val } else { return Err("Invalid solution".to_string()); };
if color_u == color_v {
return Err(format!("Invalid coloring: vertices {} and {} both have color {}",
u, v, color_u));
}
}
println!(" ✓ Coloring verified as valid");
Ok(())
}
fn analyze_graph(graph: &Graph) {
println!("\n📊 Graph Analysis: {}", graph.name);
println!(" Vertices: {}", graph.vertices.len());
println!(" Edges: {}", graph.edges.len());
let max_degree = graph.vertices.iter()
.map(|&v| graph.get_neighbors(v).len())
.max()
.unwrap_or(0);
println!(" Maximum degree: {}", max_degree);
println!(" Upper bound (degree + 1): {}", max_degree + 1);
let is_bipartite = check_bipartite(graph);
if is_bipartite {
println!(" Graph is bipartite (chromatic number ≤ 2)");
}
}
fn check_bipartite(graph: &Graph) -> bool {
if graph.vertices.is_empty() {
return true;
}
let mut model = Model::default();
let mut color_vars = HashMap::new();
for &vertex in &graph.vertices {
let var = model.int(0, 1);
color_vars.insert(vertex, var);
}
for &(u, v) in &graph.edges {
let var_u = color_vars[&u];
let var_v = color_vars[&v];
model.new(var_u.ne(var_v));
}
model.solve().is_ok()
}
fn main() {
println!("🎨 Graph Coloring Problems\n");
let graphs = vec![
Graph::cycle(5), Graph::cycle(6), Graph::complete(4), Graph::bipartite(3, 4), Graph::petersen(), Graph::planar_4_colorable(), ];
for graph in graphs {
analyze_graph(&graph);
let chromatic_number = find_chromatic_number(&graph);
println!("\n🎨 Optimal coloring:");
solve_graph_coloring(&graph, chromatic_number).unwrap();
println!("\n{}\n", "=".repeat(50));
}
println!("⚡ Performance Test: Complete Graphs");
for n in 3..=6 {
let graph = Graph::complete(n);
let start = std::time::Instant::now();
let chromatic_number = find_chromatic_number(&graph);
let duration = start.elapsed();
println!("K_{}: {} colors, solved in {:?}", n, chromatic_number, duration);
assert_eq!(chromatic_number, n, "Complete graph K_{} should need {} colors", n, n);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cycle_graphs() {
let c5 = Graph::cycle(5);
assert_eq!(find_chromatic_number(&c5), 3);
let c6 = Graph::cycle(6);
assert_eq!(find_chromatic_number(&c6), 2);
}
#[test]
fn test_complete_graphs() {
for n in 2..=5 {
let kn = Graph::complete(n);
assert_eq!(find_chromatic_number(&kn), n);
}
}
#[test]
fn test_bipartite_graphs() {
let k33 = Graph::bipartite(3, 3);
assert_eq!(find_chromatic_number(&k33), 2);
let k24 = Graph::bipartite(2, 4);
assert_eq!(find_chromatic_number(&k24), 2);
}
#[test]
fn test_petersen_graph() {
let petersen = Graph::petersen();
assert_eq!(find_chromatic_number(&petersen), 3);
}
#[test]
fn test_bipartite_detection() {
assert!(check_bipartite(&Graph::bipartite(3, 4)));
assert!(check_bipartite(&Graph::cycle(6))); assert!(!check_bipartite(&Graph::cycle(5))); assert!(!check_bipartite(&Graph::complete(3))); }
}