graph_algo_ptas/algorithm/
ptas.rs

1//! Contains the main algorithm implementing the PTAS for planar graphs.
2//!
3//! ```rust
4//! use graph_algo_ptas::generation::planar::generate;
5//! use graph_algo_ptas::algorithm::ptas::ptas;
6//! use graph_algo_ptas::algorithm::dynamic_programming::solve::DpProblem;
7//!
8//! let graph = generate(100, None).to_pet_graph();
9//! let sol = ptas(&graph, &DpProblem::max_independent_set(), 0.5);
10//! ```
11
12use super::dynamic_programming::solve::{dp_solve_hashmap_graph, DpObjective, DpProblem};
13use crate::utils::convert::UndirectedGraph;
14use arboretum_td::graph::{HashMapGraph, MutableGraph};
15use petgraph::{algo::kosaraju_scc, stable_graph::NodeIndex, visit::EdgeRef};
16use std::collections::{HashSet, VecDeque};
17
18/// Calculates an approximate solution for the given problem on the input graph.
19/// The input graph is expected to be planar.
20///
21/// The solution is guaranteed to be (1 - eps) optimal for maximization problems
22/// and (1 + eps) optimal for minimization problems.
23pub fn ptas(graph: &UndirectedGraph, prob: &DpProblem, eps: f64) -> HashSet<usize> {
24    let mut sols: Vec<HashSet<usize>> = vec![];
25
26    for ring_decomposition in get_ring_decompositions(&mut graph.clone(), eps) {
27        let mut sol: HashSet<usize> = HashSet::new();
28
29        for ring in get_component_graphs(&ring_decomposition.rings) {
30            let ring_sol = dp_solve_hashmap_graph(&ring, None, prob);
31            sol.extend(ring_sol.iter());
32        }
33
34        if prob.objective == DpObjective::Minimize {
35            let vertices_deleted = ring_decomposition
36                .vertices_deleted
37                .iter()
38                .map(|v| v.index());
39            sol.extend(vertices_deleted);
40        }
41
42        sols.push(sol);
43    }
44
45    let best_sol = match prob.objective {
46        DpObjective::Minimize => sols.iter().min_by(|s1, s2| s1.len().cmp(&s2.len())),
47        DpObjective::Maximize => sols.iter().max_by(|s1, s2| s1.len().cmp(&s2.len())),
48    };
49
50    best_sol.unwrap().clone()
51}
52
53fn get_component_graphs(graph: &UndirectedGraph) -> Vec<HashMapGraph> {
54    let mut component_graphs = vec![];
55
56    for component in &kosaraju_scc(&graph) {
57        let mut component_graph = HashMapGraph::new();
58
59        for v in component {
60            component_graph.add_vertex(v.index());
61        }
62
63        for v in component {
64            for e in graph.edges(*v) {
65                component_graph.add_edge(e.source().index(), e.target().index());
66            }
67        }
68
69        component_graphs.push(component_graph);
70    }
71
72    component_graphs
73}
74
75struct RingDecomposition {
76    rings: UndirectedGraph,
77    vertices_deleted: HashSet<NodeIndex>,
78}
79
80fn get_ring_decompositions(graph: &mut UndirectedGraph, eps: f64) -> Vec<RingDecomposition> {
81    let k = (1.0 / eps).ceil() as usize;
82    assert!(kosaraju_scc(&graph.clone()).len() == 1);
83    assert!(graph.node_count() > 0);
84
85    let mut ring_decompositions: Vec<RingDecomposition> = vec![];
86
87    for i in 0..k {
88        let mut rings = graph.clone();
89        let mut vertices_deleted = HashSet::new();
90        let mut level = 1;
91        let start = graph.node_indices().next().unwrap();
92        let mut visited: HashSet<NodeIndex<u32>> = HashSet::new();
93        let mut queue: VecDeque<NodeIndex<u32>> = VecDeque::new();
94        let sep = NodeIndex::new(usize::max_value());
95        queue.push_back(start);
96        queue.push_back(sep);
97
98        while !queue.is_empty() {
99            let current = queue.pop_front().unwrap();
100
101            if current == sep {
102                level += 1;
103
104                if !queue.is_empty() {
105                    queue.push_back(sep);
106                }
107
108                continue;
109            }
110
111            if visited.contains(&current) {
112                continue;
113            }
114            visited.insert(current);
115
116            if level % k == i {
117                vertices_deleted.insert(current);
118                rings.remove_node(current);
119            }
120
121            for n in graph.neighbors(current) {
122                queue.push_back(n);
123            }
124        }
125
126        ring_decompositions.push(RingDecomposition {
127            rings,
128            vertices_deleted,
129        });
130    }
131
132    ring_decompositions
133}
134
135#[cfg(test)]
136mod tests {
137    use super::get_ring_decompositions;
138    use crate::{
139        algorithm::{dynamic_programming::solve::DpProblem, ptas::ptas},
140        generation::{erdos_renyi::generate_petgraph, planar::generate},
141        utils::{
142            convert::{to_hash_map_graph, UndirectedGraph},
143            max_independent_set::{brute_force_max_independent_set, is_independent_set},
144            min_vertex_cover::{brute_force_min_vertex_cover, is_vertex_cover},
145        },
146    };
147    use petgraph::algo::kosaraju_scc;
148    use rand::{rngs::StdRng, Rng, SeedableRng};
149    use std::collections::HashSet;
150
151    #[test]
152    fn approximation_ratio() {
153        let seed = [2; 32];
154        let mut rng = StdRng::from_seed(seed);
155
156        let mut i = 0;
157        while i < 100 {
158            let graph = generate_petgraph(
159                rng.gen_range(1..100),
160                rng.gen_range(0.1..1.0),
161                Some(i as u64),
162            );
163
164            if kosaraju_scc(&graph).len() != 1 {
165                continue;
166            }
167
168            i += 1;
169
170            let eps = rng.gen_range(0.05..0.5) as f64;
171            let ring_decompositions = get_ring_decompositions(&mut graph.clone(), eps);
172            let mut vertices = HashSet::new();
173
174            for ring_decomposition in &ring_decompositions {
175                vertices.extend(ring_decomposition.vertices_deleted.iter());
176            }
177
178            assert!(vertices == graph.node_indices().collect());
179            assert!(ring_decompositions
180                .iter()
181                .any(|rd| { rd.vertices_deleted.len() as f64 <= eps * graph.node_count() as f64 }));
182        }
183    }
184
185    #[test]
186    fn max_independent_set_single_vertex() {
187        let mut graph = UndirectedGraph::default();
188        let v0 = graph.add_node(());
189        let sol = ptas(&graph, &DpProblem::max_independent_set(), 0.5);
190
191        assert!(sol.len() == 1);
192        assert!(sol.contains(&v0.index()));
193    }
194
195    #[test]
196    fn max_independent_set_single_edge() {
197        let mut graph = UndirectedGraph::default();
198        let v0 = graph.add_node(());
199        let v1 = graph.add_node(());
200        graph.add_edge(v0, v1, ());
201        let sol = ptas(&graph, &DpProblem::max_independent_set(), 0.5);
202        assert!(sol.len() == 1);
203        assert!(sol.contains(&v0.index()) || sol.contains(&v1.index()));
204    }
205
206    #[test]
207    fn max_independent_set_random() {
208        for n in 2..30 {
209            let graph: UndirectedGraph = generate(n, Some(n as u64)).to_pet_graph();
210            let eps = 0.5;
211            let sol = ptas(&graph, &DpProblem::max_independent_set(), eps);
212
213            assert!(is_independent_set(&to_hash_map_graph(&graph), &sol));
214
215            // if n > 15 the brute force algorithm takes too long
216            if n <= 15 {
217                let sol2 = brute_force_max_independent_set(&to_hash_map_graph(&graph));
218
219                assert!(sol.len() as f64 >= (1.0 - eps) * sol2.len() as f64);
220            }
221        }
222    }
223
224    #[test]
225    fn min_vertex_cover_single_vertex() {
226        let mut graph = UndirectedGraph::default();
227        graph.add_node(());
228        let sol = ptas(&graph, &DpProblem::min_vertex_cover(), 0.5);
229
230        assert!(sol.is_empty());
231    }
232
233    #[test]
234    fn min_vertex_cover_single_edge() {
235        let mut graph = UndirectedGraph::default();
236        let v0 = graph.add_node(());
237        let v1 = graph.add_node(());
238        graph.add_edge(v0, v1, ());
239        let sol = ptas(&graph, &DpProblem::min_vertex_cover(), 0.5);
240        assert!(sol.len() == 1);
241        assert!(sol.contains(&v0.index()) || sol.contains(&v1.index()));
242    }
243
244    #[test]
245    fn min_vertex_cover_random() {
246        for n in 2..30 {
247            let graph: UndirectedGraph = generate(n, Some(n as u64)).to_pet_graph();
248            let eps = 0.5;
249            let sol = ptas(&graph, &DpProblem::min_vertex_cover(), eps);
250
251            assert!(is_vertex_cover(&to_hash_map_graph(&graph), &sol));
252
253            // if n > 15 the brute force algorithm takes too long
254            if n <= 15 {
255                let sol2 = brute_force_min_vertex_cover(&to_hash_map_graph(&graph));
256
257                assert!(sol.len() as f64 <= (1.0 + 0.5 * eps) * sol2.len() as f64);
258            }
259        }
260    }
261}