heuristic_graph_coloring/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4/// Trait for graphs that the algorithms in this crate can work with.
5pub trait ColorableGraph {
6    /// Total number of vertices in the graph.
7    fn num_vertices(&self) -> usize;
8    /// All neighbors of vertex `vi`
9    fn neighbors(&self, vi: usize) -> &[usize];
10
11    /// Degree of vertex `vi`.
12    fn degree(&self, vi: usize) -> usize {
13        self.neighbors(vi).len()
14    }
15
16    /// Maximum degree of the graph.
17    fn max_degree(&self) -> usize {
18        (0..self.num_vertices())
19            .map(|i| self.degree(i))
20            .max()
21            .unwrap_or(0)
22    }
23}
24
25/// Graph represented as multiple `Vec`s, one for each vertex.
26#[derive(Debug, Clone)]
27pub struct VecVecGraph {
28    edges: Vec<Vec<usize>>,
29}
30
31impl ColorableGraph for &VecVecGraph {
32    fn num_vertices(&self) -> usize {
33        self.edges.len()
34    }
35
36    fn neighbors(&self, vi: usize) -> &[usize] {
37        &self.edges[vi]
38    }
39}
40
41impl VecVecGraph {
42    /// Create graph with `num_vertices` vertices and no edges.
43    pub fn new(num_vertices: usize) -> Self {
44        VecVecGraph {
45            edges: (0..num_vertices).map(|_| vec![]).collect(),
46        }
47    }
48
49    /// Add an edge between `w` and `v`.
50    ///
51    /// Note: avoid adding edges multiple times or adding an edge of a vertex to itself (a loop) as this confuses the algorithms.
52    pub fn add_edge(&mut self, w: usize, v: usize) {
53        self.edges[w].push(v);
54        self.edges[v].push(w);
55    }
56
57    /// Load a graph from a DIMACS formatted text file (`*.col`).
58    ///
59    /// Note that DIMACS files start vertex indices at 1 while this library starts vertex indices at 0.
60    pub fn from_dimacs_file(
61        path: &dyn AsRef<std::path::Path>,
62    ) -> Result<Self, Box<dyn std::error::Error>> {
63        use std::io::BufRead;
64
65        let f = std::fs::File::open(path)?;
66        let r = std::io::BufReader::new(f);
67        let mut graph = None;
68        let rgraph = &mut graph;
69        for (i, line) in r.lines().enumerate() {
70            let line = line?;
71            (|| {
72                let mut it = line.split(' ');
73                match it.next() {
74                    Some("c" | "n" | "x" | "d" | "v") | None => {}
75                    Some("p") => {
76                        // problem statement with number of ertices
77                        let format = it.next()?;
78                        if format != "edge" {
79                            return None;
80                        }
81                        let num_vertices = it.next()?.parse::<usize>().ok()?;
82                        let _num_edges = it.next()?.parse::<usize>().ok()?;
83                        if it.next().is_some() {
84                            return None;
85                        }
86                        if rgraph.is_some() {
87                            return None;
88                        }
89                        *rgraph = Some(Self::new(num_vertices));
90                    }
91                    Some("e") => {
92                        // edge
93                        let w = it.next()?.parse::<usize>().ok()? - 1;
94                        let v = it.next()?.parse::<usize>().ok()? - 1;
95                        let max = rgraph.as_ref()?.num_vertices();
96                        if w >= max || v >= max {
97                            return None;
98                        }
99                        if w != v {
100                            rgraph.as_mut()?.add_edge(w, v);
101                        }
102                    }
103                    _ => return None,
104                }
105                Some(())
106            })()
107            .ok_or(format!("invalid line {}: {}", i + 1, line))?;
108        }
109        Ok(graph.ok_or("no graph defined in file")?)
110    }
111}
112
113/// A graph in compressed format with all edges in one array.
114///
115/// Very slightly faster than [VecVecGraph] (excluding the creation time).
116#[derive(Debug, Clone)]
117pub struct CsrGraph {
118    vertices: Vec<usize>,
119    edges: Vec<usize>,
120}
121
122impl ColorableGraph for &CsrGraph {
123    fn num_vertices(&self) -> usize {
124        self.vertices.len()
125    }
126
127    fn neighbors(&self, vi: usize) -> &[usize] {
128        &self.edges[if vi == 0 { 0 } else { self.vertices[vi - 1] }..self.vertices[vi]]
129    }
130}
131
132impl<T> From<T> for CsrGraph
133where
134    T: ColorableGraph,
135{
136    fn from(graph: T) -> Self {
137        let mut vertices = Vec::with_capacity(graph.num_vertices());
138        let mut edges = vec![];
139        let mut offset = 0;
140        for i in 0..graph.num_vertices() {
141            let neighbors = graph.neighbors(i);
142            edges.extend(neighbors);
143            offset += neighbors.len();
144            vertices.push(offset);
145        }
146        CsrGraph { vertices, edges }
147    }
148}
149
150fn color_greedy_by_order(
151    graph: impl ColorableGraph,
152    order: impl Iterator<Item = usize>,
153) -> Vec<usize> {
154    let mut coloring = vec![usize::MAX; graph.num_vertices()];
155
156    for i in order {
157        let mut ncs = vec![];
158        for &n in graph.neighbors(i) {
159            let nc = coloring[n];
160            if nc != usize::MAX {
161                if nc >= ncs.len() {
162                    ncs.resize(nc + 1, false);
163                }
164                ncs[nc] = true;
165            }
166        }
167        for c in 0.. {
168            if c >= ncs.len() || !ncs[c] {
169                coloring[i] = c;
170                break;
171            }
172        }
173    }
174
175    coloring
176}
177
178/// The most simple greedy algorigthm. Colors vertices in index order by first possible color.
179///
180/// Use as baseline only, [color_greedy_by_degree] generally uses less colors in the same time.
181pub fn color_greedy_naive(graph: impl ColorableGraph) -> Vec<usize> {
182    let range = 0..graph.num_vertices();
183    color_greedy_by_order(graph, range)
184}
185
186/// Colors vertices from highest degree to lowest by first possible color.
187///
188/// Generally uses less colors than [color_greedy_naive] in about the same time.
189/// Generally uses more colors than [color_greedy_dsatur] and [color_rlf], but in less time.
190pub fn color_greedy_by_degree(graph: impl ColorableGraph) -> Vec<usize> {
191    let mut vertices: Vec<_> = (0..graph.num_vertices()).collect();
192    vertices.sort_by_key(|&i| std::cmp::Reverse(graph.neighbors(i).len()));
193    color_greedy_by_order(graph, vertices.iter().cloned())
194}
195
196#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
197struct DSatur {
198    saturation: usize,
199    degree_uncolored: usize,
200    index: std::cmp::Reverse<usize>,
201}
202
203/// Colors vertices from most colors of neighbors to least (dynamically) by first possible color. Known as DSATUR.
204///
205/// Generally uses less colors than [color_greedy_by_degree], but in more time.
206/// Generally uses more colors than [color_rlf], but in less time.
207pub fn color_greedy_dsatur(graph: impl ColorableGraph) -> Vec<usize> {
208    let mut coloring = vec![usize::MAX; graph.num_vertices()];
209
210    // to control the time complexity we don't go thorough all the neighbours of neighbours every time we color
211    let mut neighbor_coloring: Vec<Vec<bool>> = vec![];
212    neighbor_coloring.resize_with(graph.num_vertices(), Vec::new);
213
214    // initialize priority queue / heap
215    let mut dsaturs = vec![];
216    for i in 0..graph.num_vertices() {
217        dsaturs.push(DSatur {
218            saturation: 0,
219            degree_uncolored: graph.neighbors(i).len(),
220            index: std::cmp::Reverse(i),
221        });
222    }
223    let mut heap = std::collections::BTreeSet::from_iter(dsaturs.iter().cloned());
224
225    loop {
226        // get most saturated vertex
227        // TODO: use pop_last when stabilized
228        let dsatur = match heap.iter().last() {
229            None => break,
230            Some(x) => x.clone(),
231        };
232        let i = dsatur.index.0;
233        let removed = heap.remove(&dsatur);
234        assert!(removed);
235
236        // color it
237        let ncs = &neighbor_coloring[i];
238        let mut c = usize::MAX;
239        for ci in 0.. {
240            if ci >= ncs.len() || !ncs[ci] {
241                c = ci;
242                break;
243            }
244        }
245        assert!(coloring[i] == usize::MAX);
246        coloring[i] = c;
247
248        // update the counts of all neighbors
249        for &n in graph.neighbors(i) {
250            // get neighbor
251            if coloring[n] != usize::MAX {
252                continue;
253            }
254            let dsatur = &mut dsaturs[n];
255            let removed = heap.remove(dsatur);
256            assert!(removed);
257
258            // remove color from neighbor and change counts
259            let ncs = &mut neighbor_coloring[n];
260            if c >= ncs.len() {
261                ncs.resize(c + 1, false);
262            }
263            let has_color = ncs[c];
264            if !has_color {
265                ncs[c] = true;
266                dsatur.saturation += 1;
267            }
268            dsatur.degree_uncolored -= 1;
269
270            heap.insert(dsatur.clone());
271        }
272    }
273
274    coloring
275}
276
277#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
278struct Rlf {
279    next_neighbors: usize,
280    other_neighbors: std::cmp::Reverse<usize>,
281    index: std::cmp::Reverse<usize>,
282}
283
284/// Colors vertices one color at a time by foming maximal independent sets. Known as Recursive Largest First.
285///
286/// Generally uses less colors than [color_greedy_by_degree] and [color_greedy_dsatur], but in more time.
287pub fn color_rlf(graph: impl ColorableGraph) -> Vec<usize> {
288    let mut coloring = vec![usize::MAX; graph.num_vertices()];
289    let mut degree_next: Vec<usize> = vec![0; graph.num_vertices()];
290    let mut degree_other: Vec<usize> = vec![0; graph.num_vertices()];
291
292    let mut nocolor = usize::MAX;
293    let mut nextcolor = usize::MAX - 1;
294
295    for c in 0.. {
296        // reinitialize degrees
297        for i in 0..graph.num_vertices() {
298            if coloring[i] != nocolor {
299                continue;
300            }
301
302            degree_next[i] = 0;
303            degree_other[i] = graph
304                .neighbors(i)
305                .iter()
306                .filter(|&&n| coloring[n] == nocolor)
307                .count();
308        }
309
310        // get first vertex with maximum degree
311        let mut current_vertex = (0..graph.num_vertices())
312            .filter(|&i| coloring[i] == nocolor)
313            .max_by_key(|&i| (degree_other[i], std::cmp::Reverse(i)));
314        if current_vertex.is_none() {
315            break;
316        }
317
318        while let Some(i) = current_vertex {
319            debug_assert!(coloring[i] == nocolor);
320            coloring[i] = c;
321
322            // color direct neighbours
323            for &n in graph.neighbors(i) {
324                if coloring[n] != nocolor {
325                    continue;
326                }
327                coloring[n] = nextcolor;
328
329                // change degrees for indirect neighbors
330                for &n2 in graph.neighbors(n) {
331                    if coloring[n2] != nocolor {
332                        continue;
333                    }
334
335                    degree_next[n2] += 1;
336                    degree_other[n2] -= 1;
337                }
338            }
339
340            // next vertex is the one with the most shared neighbors
341            // TODO: use pop_last when stabilized?
342            current_vertex = (0..graph.num_vertices())
343                .filter(|&i| coloring[i] == nocolor)
344                .max_by_key(|&i| {
345                    (
346                        degree_next[i],
347                        std::cmp::Reverse(degree_other[i]),
348                        std::cmp::Reverse(i),
349                    )
350                });
351        }
352        std::mem::swap(&mut nextcolor, &mut nocolor);
353    }
354
355    coloring
356}
357
358/// Counts the amount of colors in `coloring` by getting the maximum color plus one.
359pub fn count_colors(coloring: &[usize]) -> usize {
360    match coloring.iter().max() {
361        None => 0,
362        Some(x) => x + 1,
363    }
364}
365
366/// Checks that the coloring is correct, else panics.
367pub fn validate_coloring(graph: impl ColorableGraph, coloring: &[usize]) {
368    for i in 0..graph.num_vertices() {
369        let c = coloring[i];
370        assert!(c != usize::MAX, "no color for vertex {i}");
371        for &n in graph.neighbors(i) {
372            assert!(
373                c != coloring[n],
374                "vertex {} and neighbor {} both have color {}",
375                i + 1,
376                n + 1,
377                c
378            );
379        }
380    }
381}
382
383/// Adjust coloring to try to make each color used a similar amount
384pub fn make_coloring_more_equitable(graph: impl ColorableGraph, coloring: &mut [usize], count_colors: usize) {
385    // get initial amounts
386    let mut amounts = vec![0; count_colors];
387    for &color in coloring.iter() {
388        amounts[color] += 1;
389    }
390
391    // loop until equilibrium
392    loop {
393        let mut changed = false;
394
395        for i in 0..graph.num_vertices() {
396            let ci = coloring[i];
397            // find available color with the least amount
398            let smallest_c = match (0..count_colors)
399                .filter(|&c| graph.neighbors(i).iter().all(|&j| coloring[j] != c))
400                .min_by_key(|&c| amounts[c])
401            {
402                None => continue,
403                Some(x) => x,
404            };
405            // change color if it would help
406            if amounts[smallest_c] < amounts[ci] - 1 {
407                coloring[i] = smallest_c;
408                amounts[ci] -= 1;
409                amounts[smallest_c] += 1;
410                changed = true;
411            }
412        }
413        if !changed {
414            break;
415        }
416    }
417}