diff --git a/Cargo.lock b/Cargo.lock index 4c21dda..767dff7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,19 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clique_graph_treewidth_heuristic" version = "0.1.0" dependencies = [ "itertools", "petgraph", + "rand", "serde", ] @@ -29,6 +36,17 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -54,6 +72,12 @@ dependencies = [ "either", ] +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + [[package]] name = "petgraph" version = "0.6.4" @@ -64,6 +88,12 @@ dependencies = [ "indexmap", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.79" @@ -82,6 +112,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "serde" version = "1.0.197" @@ -118,3 +178,9 @@ name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml index 9551f1f..48152bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ description = "A heuristic for calculating an upper bound on treewidth of graphs [dependencies] serde = { version = "1.0", features = ["derive"] } petgraph = "0.6.4" -itertools = "0.12.1" \ No newline at end of file +itertools = "0.12.1" +rand = "0.8.5" \ No newline at end of file diff --git a/src/algorithms.rs b/src/algorithms.rs index 1d339e6..8ac2fb2 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -3,3 +3,5 @@ pub mod fill_bags_along_paths; pub mod find_maximum_cliques; pub mod find_path_in_tree; pub mod find_width_of_tree_decomposition; +pub mod generate_partial_k_tree; +pub mod minimum_maximum_degree_heuristic; \ No newline at end of file diff --git a/src/algorithms/generate_partial_k_tree.rs b/src/algorithms/generate_partial_k_tree.rs new file mode 100644 index 0000000..a6cd172 --- /dev/null +++ b/src/algorithms/generate_partial_k_tree.rs @@ -0,0 +1,105 @@ +use petgraph::{ + graph::NodeIndex, + visit::{IntoEdgeReferences, IntoEdges, IntoNodeIdentifiers}, + Graph, Undirected, +}; +use rand::{seq::IteratorRandom, Rng}; + +use crate::algorithms::minimum_maximum_degree_heuristic::minimum_maximum_degree_heuristic; + +/// Generates a [k-tree](https://en.wikipedia.org/wiki/K-tree) and then randomly removes p percent of the edges +/// to get a [partial k-tree](https://en.wikipedia.org/wiki/Partial_k-tree). To guarantee a treewidth of k, +/// this procedure is repeated until the treewidth of the graph is at least k according to the minimum +/// maximum degree heuristic. +/// +/// **Caution!**: Due to the randomness involved, this function could in theory take indefinitely to generate +/// a partial k-tree with the wished treewidth. +/// +/// If p > 100 all edges will be removed. The Rng is passed in to increase performance when calling the function multiple times in a row. +/// +/// Returns None if k > n +pub fn generate_partial_k_tree_with_guaranteed_treewidth( + k: usize, + n: usize, + p: usize, + rng: &mut impl Rng, +) -> Option> { + loop { + if let Some(mut graph) = generate_partial_k_tree(k, n, p, rng) { + if minimum_maximum_degree_heuristic(&graph) == k { + return Some(graph); + } + } else { + return None; + } + } +} + +/// Generates a [k-tree](https://en.wikipedia.org/wiki/K-tree) and then randomly removes p percent of the edges +/// to get a [partial k-tree](https://en.wikipedia.org/wiki/Partial_k-tree). +/// If p > 100 all edges will be removed. The Rng is passed in to increase performance when calling the function multiple times in a row. +/// +/// Returns None if k > n +pub fn generate_partial_k_tree( + k: usize, + n: usize, + p: usize, + rng: &mut impl Rng, +) -> Option> { + if let Some(mut graph) = generate_k_tree(k, n) { + // The number of edges in a k-tree + let number_of_edges = k * (k - 1) / 2 + k * (n - k); + + // Remove p percent of nodes + for edge_to_be_removed in graph + .edge_indices() + .choose_multiple(rng, ((number_of_edges * p) / 100).min(number_of_edges)) + { + graph.remove_edge(edge_to_be_removed); + } + + Some(graph) + } else { + None + } +} + +/// Generates a [k-tree](https://en.wikipedia.org/wiki/K-tree) with n vertices and k in the definition. +/// Returns None if k > n +fn generate_k_tree(k: usize, n: usize) -> Option> { + if k > n { + None + } else { + let mut graph = generate_complete_graph(k); + + // Add the missing n-k vertices + for _ in k..n { + let new_vertex = graph.add_node(0); + for old_vertex_index in graph + .node_identifiers() + .choose_multiple(&mut rand::thread_rng(), k) + { + graph.add_edge(new_vertex, old_vertex_index, 0); + } + } + + Some(graph) + } +} + +fn generate_complete_graph(k: usize) -> Graph { + let mut graph: Graph = + petgraph::Graph::new_undirected(); + + // Add k nodes to the graph + let nodes: Vec = (0..k + 1).map(|_| graph.add_node(0)).collect(); + + // Connect each node to every other node + for i in 0..k + 1 { + for j in i + 1..k + 1 { + graph.add_edge(nodes[i], nodes[j], 0); + } + } + + graph +} diff --git a/src/algorithms/minimum_maximum_degree_heuristic.rs b/src/algorithms/minimum_maximum_degree_heuristic.rs new file mode 100644 index 0000000..8469835 --- /dev/null +++ b/src/algorithms/minimum_maximum_degree_heuristic.rs @@ -0,0 +1,25 @@ +use petgraph::{visit::IntoNodeIdentifiers, Graph, Undirected}; + +pub fn minimum_maximum_degree_heuristic( + graph: &Graph, +) -> usize { + let mut max_min = 0; + let mut graph_copy = graph.clone(); + + while graph_copy.node_count() >= 2 { + let min_degree_vertex = graph_copy + .node_identifiers() + .min_by_key(|id| graph_copy.neighbors(*id).collect::>().len()) + .expect("Graph should have at least 2 nodes"); + + max_min = max_min.max( + graph_copy + .neighbors(min_degree_vertex) + .collect::>() + .len(), + ); + graph_copy.remove_node(min_degree_vertex); + } + + max_min +}