From d37c7c48bc13661f73da9a79719b19adf94f0efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raoul=20Luqu=C3=A9?= Date: Fri, 10 May 2024 12:52:31 +0200 Subject: [PATCH] Added option to choose hasher in order to enable deterministic calculations --- Cargo.lock | 8 ++ Cargo.toml | 8 +- dimacs_benchmarks/Cargo.toml | 3 +- dimacs_benchmarks/src/main.rs | 75 +++++++++++++++++-- k_tree_benchmarks/Cargo.toml | 3 +- k_tree_benchmarks/src/main.rs | 18 +++-- treewidth_heuristic/Cargo.toml | 3 +- .../src/check_tree_decomposition.rs | 19 +++-- .../clique_graph_edge_weight_heuristics.rs | 40 +++++----- .../src/compute_treewidth_upper_bound.rs | 51 +++++++------ .../src/construct_clique_graph.rs | 27 +++---- .../src/fill_bags_along_paths.rs | 32 ++++---- .../src/fill_bags_while_generating_mst.rs | 37 ++++----- .../src/find_connected_components.rs | 18 +++-- .../src/find_maximum_cliques.rs | 31 ++++---- treewidth_heuristic/src/find_path_in_tree.rs | 10 ++- .../src/find_width_of_tree_decomposition.rs | 4 +- treewidth_heuristic/src/lib.rs | 41 ++++++++++ 18 files changed, 293 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d99e4c..41bfd20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,7 @@ dependencies = [ "dimacs_petgraph_parser", "env_logger", "petgraph", + "rustc-hash", "serde", "treewidth_heuristic", ] @@ -187,6 +188,7 @@ version = "0.1.0" dependencies = [ "petgraph", "rand", + "rustc-hash", "serde", "treewidth_heuristic", ] @@ -302,6 +304,11 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "git+https://github.com/rust-lang/rustc-hash#e155548d4bc95fb98e214506d79a87a7bacdb025" + [[package]] name = "serde" version = "1.0.200" @@ -341,6 +348,7 @@ dependencies = [ "log", "petgraph", "rand", + "rustc-hash", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6a38f69..2d3501c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,10 @@ members = [ ] resolver = "2" -# Enable a small amount of optimization in debug mode +# Optimization in debug mode for own code [profile.dev] -opt-level = 3 +opt-level = 1 -# Enable high optimizations for dependencies (incl. Bevy), but not for our code: +# Optimiziation in debug mode for dependencies [profile.dev.package."*"] -opt-level = 3 \ No newline at end of file +opt-level = 1 \ No newline at end of file diff --git a/dimacs_benchmarks/Cargo.toml b/dimacs_benchmarks/Cargo.toml index e7c2312..1f52aea 100644 --- a/dimacs_benchmarks/Cargo.toml +++ b/dimacs_benchmarks/Cargo.toml @@ -12,4 +12,5 @@ serde = { version = "1.0", features = ["derive"] } petgraph = "0.6.4" treewidth_heuristic = { path = "../treewidth_heuristic", version = "0.1.0" } dimacs_petgraph_parser = { git = "https://github.com/RaoulLuque/dimacs-petgraph-parser" } -env_logger = "0.11.3" \ No newline at end of file +env_logger = "0.11.3" +rustc-hash = { git = "https://github.com/rust-lang/rustc-hash"} \ No newline at end of file diff --git a/dimacs_benchmarks/src/main.rs b/dimacs_benchmarks/src/main.rs index a21a4d5..33fd398 100644 --- a/dimacs_benchmarks/src/main.rs +++ b/dimacs_benchmarks/src/main.rs @@ -8,11 +8,19 @@ use treewidth_heuristic::{ compute_treewidth_upper_bound_not_connected, TreewidthComputationMethod, }; +// Debug version +#[cfg(debug_assertions)] +type Hasher = std::hash::BuildHasherDefault; + +// Non-debug version +#[cfg(not(debug_assertions))] +type Hasher = std::hash::RandomState; + fn main() { env_logger::init(); let computation_type: TreewidthComputationMethod = TreewidthComputationMethod::FillWhilstMST; - let heuristic = treewidth_heuristic::least_difference_heuristic; + let heuristic = treewidth_heuristic::negative_intersection_heuristic; // Opening and writing to log file let mut benchmark_log_file = @@ -33,14 +41,69 @@ fn main() { let graph: Graph = read_graph(graph_file).expect("Graph should be in correct format"); + // DEBUG + // { + // use treewidth_heuristic::*; + + // // Find cliques in initial graph + // let cliques_one: Vec> = itertools::Itertools::sorted( + // find_maximum_cliques::, _, Hasher>(&graph), + // ) + // .collect(); + // let cliques_two: Vec> = itertools::Itertools::sorted( + // find_maximum_cliques::, _, Hasher>(&graph), + // ) + // .collect(); + + // assert!( + // cliques_one.eq(&cliques_two), + // "Cliques one: {:?} and \n cliques two: {:?}", + // cliques_one, + // cliques_two + // ); + + // let clique_graph_one: Graph<_, _, _> = + // construct_clique_graph(cliques_one, heuristic); + // let clique_graph_two: Graph<_, _, _> = + // construct_clique_graph(cliques_two, heuristic); + // assert!(petgraph::algo::is_isomorphic_matching( + // &clique_graph_one, + // &clique_graph_two, + // |a, b| a.eq(b), + // |a, b| a.eq(b) + // )); + + // let mut clique_graph_tree_one: Graph< + // std::collections::HashSet, + // i32, + // petgraph::prelude::Undirected, + // > = petgraph::data::FromElements::from_elements( + // petgraph::algo::min_spanning_tree(&clique_graph_one), + // ); + // let mut clique_graph_tree_two: Graph< + // std::collections::HashSet, + // i32, + // petgraph::prelude::Undirected, + // > = petgraph::data::FromElements::from_elements( + // petgraph::algo::min_spanning_tree(&clique_graph_two), + // ); + + // let clique_graph_tree_before_filling_one = clique_graph_tree_one.clone(); + // let clique_graph_tree_before_filling_two = clique_graph_tree_two.clone(); + + // fill_bags_along_paths(&mut clique_graph_tree_one); + // fill_bags_along_paths(&mut clique_graph_tree_two); + // } + println!("Starting calculation on graph: {:?}", graph_file_name); // Time the calculation let start = SystemTime::now(); - let computed_treewidth = compute_treewidth_upper_bound_not_connected( - &graph, - heuristic, - computation_type, - false, + let computed_treewidth = compute_treewidth_upper_bound_not_connected::< + _, + _, + Hasher, + >( + &graph, heuristic, computation_type, false ); benchmark_log_file diff --git a/k_tree_benchmarks/Cargo.toml b/k_tree_benchmarks/Cargo.toml index 9ddc03c..e037cad 100644 --- a/k_tree_benchmarks/Cargo.toml +++ b/k_tree_benchmarks/Cargo.toml @@ -11,4 +11,5 @@ repository = "https://github.com/RaoulLuque/treewidth-heuristic-clique-graph" serde = { version = "1.0", features = ["derive"] } petgraph = "0.6.4" treewidth_heuristic = { path = "../treewidth_heuristic", version = "0.1.0" } -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" +rustc-hash = { git = "https://github.com/rust-lang/rustc-hash"} \ No newline at end of file diff --git a/k_tree_benchmarks/src/main.rs b/k_tree_benchmarks/src/main.rs index 146af49..abbcd69 100644 --- a/k_tree_benchmarks/src/main.rs +++ b/k_tree_benchmarks/src/main.rs @@ -8,11 +8,19 @@ use petgraph::Graph; use std::time::SystemTime; use treewidth_heuristic::{compute_treewidth_upper_bound, TreewidthComputationMethod}; +// Debug version +#[cfg(debug_assertions)] +type Hasher = std::hash::BuildHasherDefault; + +// Non-debug version +#[cfg(not(debug_assertions))] +type Hasher = std::hash::RandomState; + fn main() { let k = 10; let n = 100; let p = 10; - let edge_heuristic = treewidth_heuristic::least_difference_heuristic; + let edge_heuristic = treewidth_heuristic::least_difference_heuristic::; let number_of_trees = 100; let computation_type = TreewidthComputationMethod::FillWhilstMST; @@ -133,16 +141,16 @@ fn main() { // Converting dot files to pdf in bulk: // FullPath -type f -name "*.dot" | xargs dot -Tpdf -O -fn create_dot_files( +fn create_dot_files( graph: &Graph, - clique_graph: &Graph, i32, petgraph::prelude::Undirected>, + clique_graph: &Graph, i32, petgraph::prelude::Undirected>, clique_graph_tree_after_filling_up: &Graph< - HashSet, + HashSet, i32, petgraph::prelude::Undirected, >, clique_graph_tree_before_filling_up: &Option< - Graph, i32, petgraph::prelude::Undirected>, + Graph, i32, petgraph::prelude::Undirected>, >, i: usize, name: &str, diff --git a/treewidth_heuristic/Cargo.toml b/treewidth_heuristic/Cargo.toml index c08b7e1..8ece29d 100644 --- a/treewidth_heuristic/Cargo.toml +++ b/treewidth_heuristic/Cargo.toml @@ -12,4 +12,5 @@ repository = "https://github.com/RaoulLuque/treewidth-heuristic-clique-graph" petgraph = "0.6.4" itertools = "0.12.1" rand = "0.8.5" -log = "0.4" \ No newline at end of file +log = "0.4" +rustc-hash = { git = "https://github.com/rust-lang/rustc-hash"} \ No newline at end of file diff --git a/treewidth_heuristic/src/check_tree_decomposition.rs b/treewidth_heuristic/src/check_tree_decomposition.rs index 5eee7e7..30cddf2 100644 --- a/treewidth_heuristic/src/check_tree_decomposition.rs +++ b/treewidth_heuristic/src/check_tree_decomposition.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + hash::BuildHasher, +}; use itertools::Itertools; use log::error; @@ -11,15 +14,15 @@ use petgraph::{ /// is valid, returns false otherwise. /// /// If predecessor map and clique graph map are passed, gives additional in the case that it is a faulty tree decomposition. -pub fn check_tree_decomposition( +pub fn check_tree_decomposition( starting_graph: &Graph, tree_decomposition_graph: &Graph< - std::collections::HashSet, + std::collections::HashSet, i32, petgraph::prelude::Undirected, >, - predecessor_map: &Option>, - clique_graph_map: &Option>>, + predecessor_map: &Option>, + clique_graph_map: &Option, S>>, ) -> bool { // Check if (1) from tree decomposition is satisfied (all vertices from starting graph appear in a bag in // tree decomposition graph) @@ -37,7 +40,7 @@ pub fn check_tree_decomposition( // both its vertices) for edge_reference in starting_graph.edge_references() { let (vertex_one, vertex_two) = (edge_reference.source(), edge_reference.target()); - let mut edge_as_set: HashSet = HashSet::new(); + let mut edge_as_set: HashSet<_, S> = Default::default(); edge_as_set.insert(vertex_one); edge_as_set.insert(vertex_two); let mut edge_is_contained = false; @@ -66,7 +69,7 @@ pub fn check_tree_decomposition( second_tuple.weight(), ); - let intersection_set: HashSet<_> = + let intersection_set: HashSet<_, S> = first_weight.intersection(second_weight).cloned().collect(); assert_eq!( @@ -100,7 +103,7 @@ pub fn check_tree_decomposition( .expect("Bag for the vertex should exist") .is_superset(&intersection_set) { - let vertices_missing_along_path: HashSet<_> = intersection_set + let vertices_missing_along_path: HashSet<_, S> = intersection_set .difference(tree_decomposition_graph.node_weight(node_index).unwrap()) .collect(); diff --git a/treewidth_heuristic/src/clique_graph_edge_weight_heuristics.rs b/treewidth_heuristic/src/clique_graph_edge_weight_heuristics.rs index ea65afd..ef6b445 100644 --- a/treewidth_heuristic/src/clique_graph_edge_weight_heuristics.rs +++ b/treewidth_heuristic/src/clique_graph_edge_weight_heuristics.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::{collections::HashSet, hash::BuildHasher}; use petgraph::graph::NodeIndex; @@ -7,50 +7,50 @@ pub fn neutral_heuristic(_: &HashSet, _: &HashSet) -> i32 } // Classic -pub fn negative_intersection_heuristic( - first_vertex: &HashSet, - second_vertex: &HashSet, +pub fn negative_intersection_heuristic( + first_vertex: &HashSet, + second_vertex: &HashSet, ) -> i32 { -(first_vertex .intersection(second_vertex) - .collect::>() + .collect::>() .len() as i32) } -pub fn positive_intersection_heuristic( - first_vertex: &HashSet, - second_vertex: &HashSet, +pub fn positive_intersection_heuristic( + first_vertex: &HashSet, + second_vertex: &HashSet, ) -> i32 { first_vertex .intersection(second_vertex) - .collect::>() + .collect::>() .len() as i32 } -pub fn disjoint_union_heuristic( - first_vertex: &HashSet, - second_vertex: &HashSet, +pub fn disjoint_union_heuristic( + first_vertex: &HashSet, + second_vertex: &HashSet, ) -> i32 { (first_vertex.len() + second_vertex.len()) as i32 } -pub fn union_heuristic( - first_vertex: &HashSet, - second_vertex: &HashSet, +pub fn union_heuristic( + first_vertex: &HashSet, + second_vertex: &HashSet, ) -> i32 { first_vertex .union(second_vertex) - .collect::>() + .collect::>() .len() as i32 } // Classic alt? -pub fn least_difference_heuristic( - first_vertex: &HashSet, - second_vertex: &HashSet, +pub fn least_difference_heuristic( + first_vertex: &HashSet, + second_vertex: &HashSet, ) -> i32 { first_vertex .symmetric_difference(second_vertex) - .collect::>() + .collect::>() .len() as i32 } diff --git a/treewidth_heuristic/src/compute_treewidth_upper_bound.rs b/treewidth_heuristic/src/compute_treewidth_upper_bound.rs index 5f130a4..6f0f469 100644 --- a/treewidth_heuristic/src/compute_treewidth_upper_bound.rs +++ b/treewidth_heuristic/src/compute_treewidth_upper_bound.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + hash::{BuildHasher, RandomState}, +}; use crate::*; use itertools::Itertools; @@ -20,21 +23,23 @@ pub enum TreewidthComputationMethod { /// Can also check the tree decomposition for correctness after computation which will up to double /// the running time. If so, will panic if the tree decomposition if incorrect returning the vertices /// and path that is faulty. -pub fn compute_treewidth_upper_bound( +pub fn compute_treewidth_upper_bound( graph: &Graph, - edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, + edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, treewidth_computation_method: TreewidthComputationMethod, check_tree_decomposition_bool: bool, ) -> ( - Graph, i32, Undirected>, - Graph, i32, Undirected>, - Option, i32, Undirected>>, - Option>, - Option>>, + Graph, i32, Undirected>, + Graph, i32, Undirected>, + Option, i32, Undirected>>, + Option>, + Option, S>>, usize, ) { // Find cliques in initial graph - let cliques: Vec> = find_maximum_cliques::, _>(graph).sorted().collect(); + let cliques: Vec> = find_maximum_cliques::, _, S>(graph) + .sorted() + .collect(); let ( clique_graph_tree_after_filling_up, @@ -50,7 +55,7 @@ pub fn compute_treewidth_upper_bound( // println!("Initial clique graph: {:?}", clique_graph); let mut clique_graph_tree: Graph< - std::collections::HashSet, + std::collections::HashSet, i32, petgraph::prelude::Undirected, > = petgraph::data::FromElements::from_elements(petgraph::algo::min_spanning_tree( @@ -60,7 +65,7 @@ pub fn compute_treewidth_upper_bound( // DEBUG let mut clique_graph_tree_copy: Graph< - std::collections::HashSet, + std::collections::HashSet, i32, petgraph::prelude::Undirected, > = petgraph::data::FromElements::from_elements(petgraph::algo::min_spanning_tree( @@ -94,7 +99,7 @@ pub fn compute_treewidth_upper_bound( construct_clique_graph(cliques, edge_weight_heuristic); let mut clique_graph_tree: Graph< - std::collections::HashSet, + std::collections::HashSet, i32, petgraph::prelude::Undirected, > = petgraph::data::FromElements::from_elements(petgraph::algo::min_spanning_tree( @@ -117,10 +122,10 @@ pub fn compute_treewidth_upper_bound( construct_clique_graph_with_bags(cliques, edge_weight_heuristic); let clique_graph_tree: Graph< - std::collections::HashSet, + std::collections::HashSet, i32, petgraph::prelude::Undirected, - > = fill_bags_while_generating_mst::( + > = fill_bags_while_generating_mst::( &clique_graph, edge_weight_heuristic, clique_graph_map, @@ -152,13 +157,17 @@ pub fn compute_treewidth_upper_bound( /// Computes an upper bound for the treewidth returning the maximum [compute_treewidth_upper_bound] on the /// components -pub fn compute_treewidth_upper_bound_not_connected( +pub fn compute_treewidth_upper_bound_not_connected< + N: Clone, + E: Clone, + S: Default + BuildHasher + Clone, +>( graph: &Graph, - edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, + edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, use_predecessor_map_to_fill_bags: TreewidthComputationMethod, check_tree_decomposition_bool: bool, ) -> usize { - let components = find_connected_components::, _, _>(graph); + let components = find_connected_components::, _, _, S>(graph); let mut computed_treewidth: usize = 0; for component in components { @@ -188,14 +197,14 @@ mod tests { fn test_treewidth_heuristic_check_tree_decomposition() { for i in 0..3 { let test_graph = setup_test_graph(i); - let _ = compute_treewidth_upper_bound_not_connected( + let _ = compute_treewidth_upper_bound_not_connected::<_, _, RandomState>( &test_graph.graph, neutral_heuristic, TreewidthComputationMethod::MSTAndUseTreeStructure, true, ); - let _ = compute_treewidth_upper_bound_not_connected( + let _ = compute_treewidth_upper_bound_not_connected::<_, _, RandomState>( &test_graph.graph, neutral_heuristic, TreewidthComputationMethod::MSTAndFill, @@ -208,7 +217,7 @@ mod tests { fn test_treewidth_heuristic_and_check_result_neutral_weight_heuristic() { for i in vec![0, 2] { let test_graph = setup_test_graph(i); - let computed_treewidth = compute_treewidth_upper_bound_not_connected( + let computed_treewidth = compute_treewidth_upper_bound_not_connected::<_, _, RandomState>( &test_graph.graph, neutral_heuristic, TreewidthComputationMethod::MSTAndUseTreeStructure, @@ -216,7 +225,7 @@ mod tests { ); assert_eq!(computed_treewidth, test_graph.treewidth); - let computed_treewidth = compute_treewidth_upper_bound_not_connected( + let computed_treewidth = compute_treewidth_upper_bound_not_connected::<_, _, RandomState>( &test_graph.graph, neutral_heuristic, TreewidthComputationMethod::MSTAndFill, diff --git a/treewidth_heuristic/src/construct_clique_graph.rs b/treewidth_heuristic/src/construct_clique_graph.rs index 99fa526..45db4cb 100644 --- a/treewidth_heuristic/src/construct_clique_graph.rs +++ b/treewidth_heuristic/src/construct_clique_graph.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::hash::BuildHasher; use petgraph::graph::NodeIndex; use petgraph::Graph; @@ -6,15 +7,15 @@ use petgraph::Graph; /// Constructs a clique graph given cliques of a graph. /// The clique graph consists of vertices which represent the cliques (bags) /// and edges that connect two vertices if the intersection of the corresponding cliques is not empty. -pub fn construct_clique_graph( +pub fn construct_clique_graph( cliques: OuterIterator, - edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, -) -> Graph, i32, petgraph::prelude::Undirected> + edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, +) -> Graph, i32, petgraph::prelude::Undirected> where OuterIterator: IntoIterator, InnerCollection: IntoIterator, { - let mut result_graph: Graph, i32, petgraph::prelude::Undirected> = + let mut result_graph: Graph, i32, petgraph::prelude::Undirected> = Graph::new_undirected(); for clique in cliques { let vertex_index = result_graph.add_node(HashSet::from_iter(clique.into_iter())); @@ -51,21 +52,21 @@ where /// Returns a tuple of the clique graph and a HashMap mapping the vertices in the original graph (the /// vertices from the bags) to HashSets containing the NodeIndices of all the Bags in the Clique Graph /// that contain the vertex from the original graph. -pub fn construct_clique_graph_with_bags( +pub fn construct_clique_graph_with_bags( cliques: OuterIterator, - edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, + edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, ) -> ( - Graph, i32, petgraph::prelude::Undirected>, - HashMap>, + Graph, i32, petgraph::prelude::Undirected>, + HashMap, S>, ) where OuterIterator: IntoIterator, InnerCollection: IntoIterator, InnerCollection: Clone, { - let mut result_graph: Graph, i32, petgraph::prelude::Undirected> = + let mut result_graph: Graph, i32, petgraph::prelude::Undirected> = Graph::new_undirected(); - let mut result_map: HashMap> = HashMap::new(); + let mut result_map: HashMap, S> = Default::default(); for clique in cliques { let vertex_index = result_graph.add_node(HashSet::from_iter(clique.clone().into_iter())); @@ -98,15 +99,15 @@ where (result_graph, result_map) } -fn add_node_index_to_bag_in_hashmap( - map: &mut HashMap>, +fn add_node_index_to_bag_in_hashmap( + map: &mut HashMap, S>, vertex_in_graph: NodeIndex, vertex_in_clique_graph: NodeIndex, ) { if let Some(set) = map.get_mut(&vertex_in_graph) { set.insert(vertex_in_clique_graph); } else { - let mut set = HashSet::new(); + let mut set: HashSet = Default::default(); set.insert(vertex_in_clique_graph); map.insert(vertex_in_graph, set); } diff --git a/treewidth_heuristic/src/fill_bags_along_paths.rs b/treewidth_heuristic/src/fill_bags_along_paths.rs index 7804b42..d4dde8e 100644 --- a/treewidth_heuristic/src/fill_bags_along_paths.rs +++ b/treewidth_heuristic/src/fill_bags_along_paths.rs @@ -5,6 +5,7 @@ use std::{ cmp::Ordering, collections::{BTreeSet, HashMap, HashSet}, fmt::Debug, + hash::BuildHasher, }; #[derive(PartialEq, Eq, Debug)] @@ -36,8 +37,8 @@ impl PartialOrd for Predecessor { /// Given a tree graph with bags (HashSets) as Vertices, checks all 2-combinations of bags for non-empty-intersection /// and inserts the intersecting nodes in all bags that are along the (unique) path of the two bags in the tree. -pub fn fill_bags_along_paths( - graph: &mut Graph, E, petgraph::prelude::Undirected>, +pub fn fill_bags_along_paths( + graph: &mut Graph, E, petgraph::prelude::Undirected>, ) { // Finding out which paths between bags have to be checked for mut vec in graph.node_indices().combinations(2) { @@ -83,13 +84,16 @@ pub fn fill_bags_along_paths( /// Given a tree graph with bags (HashSets) as Vertices, checks all 2-combinations of bags for non-empty-intersection /// and inserts the intersecting nodes in all bags that are along the (unique) path of the two bags in the tree. -pub fn fill_bags_along_paths_using_structure( - graph: &mut Graph, E, petgraph::prelude::Undirected>, - clique_graph_map: &HashMap>, -) -> HashMap { +pub fn fill_bags_along_paths_using_structure< + E: Copy + Default + Debug, + S: Default + BuildHasher, +>( + graph: &mut Graph, E, petgraph::prelude::Undirected>, + clique_graph_map: &HashMap, S>, +) -> HashMap { info!("Building tree structure"); - let mut tree_predecessor_map: HashMap = HashMap::new(); + let mut tree_predecessor_map: HashMap = Default::default(); let root = graph .node_indices() .max_by_key(|v| graph.neighbors(*v).collect::>().len()) @@ -129,9 +133,9 @@ pub fn fill_bags_along_paths_using_structure( /// Sets up the predecessor map such that each node has a predecessor going back to the root node. /// Additionally there is an index, indicating the depth level at which the predecessor is (root is 0) -fn setup_predecessors( - graph: &Graph, E, petgraph::prelude::Undirected>, - predecessors_map: &mut HashMap, +fn setup_predecessors( + graph: &Graph, E, petgraph::prelude::Undirected>, + predecessors_map: &mut HashMap, root: NodeIndex, ) { let mut stack: Vec<(NodeIndex, usize)> = Vec::new(); @@ -160,11 +164,11 @@ fn setup_predecessors( ); } -fn fill_bags_until_common_predecessor( - clique_graph: &mut Graph, E, petgraph::prelude::Undirected>, - predecessors_map: &HashMap, +fn fill_bags_until_common_predecessor( + clique_graph: &mut Graph, E, petgraph::prelude::Undirected>, + predecessors_map: &HashMap, vertex_in_initial_graph: &NodeIndex, - vertices_in_clique_graph: &HashSet, + vertices_in_clique_graph: &HashSet, ) { // TO DO ? // Maybe optimize by not filling up vertices_in_clique_graph, but inserting their predecessors already diff --git a/treewidth_heuristic/src/fill_bags_while_generating_mst.rs b/treewidth_heuristic/src/fill_bags_while_generating_mst.rs index ccb45bb..0834cc5 100644 --- a/treewidth_heuristic/src/fill_bags_while_generating_mst.rs +++ b/treewidth_heuristic/src/fill_bags_while_generating_mst.rs @@ -1,27 +1,30 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + hash::BuildHasher, +}; use petgraph::{graph::NodeIndex, Graph, Undirected}; -pub fn fill_bags_while_generating_mst( - clique_graph: &Graph, i32, Undirected>, - edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, - clique_graph_map: HashMap>, -) -> Graph, i32, Undirected> { - let mut result_graph: Graph, i32, Undirected> = Graph::new_undirected(); +pub fn fill_bags_while_generating_mst( + clique_graph: &Graph, i32, Undirected>, + edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, + clique_graph_map: HashMap, S>, +) -> Graph, i32, Undirected> { + let mut result_graph: Graph, i32, Undirected> = Graph::new_undirected(); // Maps the vertex indices from the clique graph to the corresponding vertex indices in the result graph - let mut node_index_map: HashMap = HashMap::new(); + let mut node_index_map: HashMap = Default::default(); let mut vertex_iter = clique_graph.node_indices(); let first_vertex_clique = vertex_iter.next().expect("Graph shouldn't be empty"); // Keeps track of the remaining vertices from the clique graph that still need to be added to // the result_graph - let mut clique_graph_remaining_vertices: HashSet = vertex_iter.collect(); + let mut clique_graph_remaining_vertices: HashSet = vertex_iter.collect(); // Keeps track of the vertices that could be added to the current sub-tree-graph // First Tuple entry is node_index from the result graph that has an outgoing edge // Second tuple entry is node_index from the clique graph that is the interesting vertex - let mut currently_interesting_vertices: HashSet<(NodeIndex, NodeIndex)> = HashSet::new(); + let mut currently_interesting_vertices: HashSet<(NodeIndex, NodeIndex), S> = Default::default(); let first_vertex_res = result_graph.add_node( clique_graph @@ -109,10 +112,10 @@ pub fn fill_bags_while_generating_mst( /// /// Panics: Panics if there is no path between start and end_vertex, especially in the case that /// one of the vertices is not contained in the graph -fn fill_bags( +fn fill_bags( start_vertex: NodeIndex, end_vertex: NodeIndex, - graph: &mut Graph, i32, Undirected>, + graph: &mut Graph, i32, Undirected>, vertex_to_be_insert_from_starting_graph: NodeIndex, ) { let mut path: Vec<_> = petgraph::algo::simple_paths::all_simple_paths::, _>( @@ -142,11 +145,11 @@ fn fill_bags( /// /// Returns a tuple with a node index from the result graph in the first and node index from the clique graph /// in the second entry -fn find_cheapest_vertex( - clique_graph: &Graph, i32, Undirected>, - result_graph: &Graph, i32, Undirected>, - edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, - currently_interesting_vertices: &HashSet<(NodeIndex, NodeIndex)>, +fn find_cheapest_vertex( + clique_graph: &Graph, i32, Undirected>, + result_graph: &Graph, i32, Undirected>, + edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, + currently_interesting_vertices: &HashSet<(NodeIndex, NodeIndex), S>, ) -> (NodeIndex, NodeIndex) { *currently_interesting_vertices .iter() diff --git a/treewidth_heuristic/src/find_connected_components.rs b/treewidth_heuristic/src/find_connected_components.rs index 9dc3721..a072403 100644 --- a/treewidth_heuristic/src/find_connected_components.rs +++ b/treewidth_heuristic/src/find_connected_components.rs @@ -1,6 +1,7 @@ use petgraph::graph::NodeIndex; use petgraph::visit::{EdgeCount, IntoNeighbors, IntoNodeIdentifiers}; use petgraph::{Graph, Undirected}; +use std::hash::BuildHasher; use std::iter::from_fn; use std::{collections::HashSet, hash::Hash}; @@ -9,18 +10,18 @@ use std::{collections::HashSet, hash::Hash}; /// Uses breadth first search starting at vertices to find components /// /// Adapted from [networkx connected_components](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.components.connected_components.html) -pub fn find_connected_components( +pub fn find_connected_components( graph: &Graph, ) -> impl Iterator + '_ where TargetColl: FromIterator, { - let mut seen_vertices: HashSet = HashSet::new(); + let mut seen_vertices: HashSet = crate::hashset![]; from_fn(move || { for vertex in graph.node_identifiers() { if !seen_vertices.contains(&vertex) { - let component = breadth_first_search(&graph, vertex); + let component = breadth_first_search::<_, S>(&graph, vertex); seen_vertices.extend(component.iter().cloned()); return Some(component.into_iter().collect::()); } @@ -30,7 +31,10 @@ where } /// Breadth first search implemented iteratively using a stack -fn breadth_first_search(graph: &G, source: G::NodeId) -> HashSet +fn breadth_first_search( + graph: &G, + source: G::NodeId, +) -> HashSet where G: EdgeCount, G: IntoNeighbors, @@ -39,7 +43,7 @@ where { let edge_count = graph.edge_count(); - let mut seen = HashSet::new(); + let mut seen: HashSet<_, S> = Default::default(); seen.insert(source); let mut next_level = Vec::new(); next_level.push(source); @@ -70,6 +74,8 @@ where #[cfg(test)] mod tests { + use std::hash::RandomState; + use super::*; #[test] @@ -78,7 +84,7 @@ mod tests { let test_graph = crate::tests::setup_test_graph(i); let mut components: Vec> = - find_connected_components::, _, _>(&test_graph.graph).collect(); + find_connected_components::, _, _, RandomState>(&test_graph.graph).collect(); for i in 0..components.len() { components[i].sort(); diff --git a/treewidth_heuristic/src/find_maximum_cliques.rs b/treewidth_heuristic/src/find_maximum_cliques.rs index 84e7b45..baff647 100644 --- a/treewidth_heuristic/src/find_maximum_cliques.rs +++ b/treewidth_heuristic/src/find_maximum_cliques.rs @@ -1,12 +1,15 @@ use itertools::Itertools; use petgraph::visit::{GraphBase, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount}; +use std::hash::BuildHasher; use std::iter::from_fn; use std::{collections::HashSet, hash::Hash}; /// Returns an iterator that produces all maximum cliques in the given graph in arbitrary order. /// /// This algorithm is adapted from . -pub fn find_maximum_cliques(graph: G) -> impl Iterator +pub fn find_maximum_cliques( + graph: G, +) -> impl Iterator where G: NodeCount, G: IntoNeighborsDirected, @@ -21,7 +24,7 @@ where // last elem is list of children of last visited node let mut stack = vec![]; - let mut atcc: HashSet = graph.node_identifiers().collect(); + let mut atcc: HashSet = graph.node_identifiers().collect(); let u = *atcc .iter() @@ -33,10 +36,10 @@ where .expect("Graph shouldn't be empty"); let mut promising_candidates: Vec = atcc.iter().cloned().collect(); - let neighbors_u: HashSet = graph.neighbors(u).collect(); + let neighbors_u: HashSet = graph.neighbors(u).collect(); promising_candidates.retain(|v| !neighbors_u.contains(v)); - let mut candidates: HashSet = graph.node_identifiers().collect(); + let mut candidates: HashSet = graph.node_identifiers().collect(); // current clique - Q : Clique that is currently being constructed // candidates - cand : Current candidates that could be added to Q (current Clique) - special for handling cliques with the given set of nodes @@ -57,7 +60,7 @@ where candidates.remove(&q); - let adjacent_to_q: HashSet = graph.neighbors(q).collect(); + let adjacent_to_q: HashSet = graph.neighbors(q).collect(); let mut atcc_q = atcc.clone(); atcc_q.retain(|v| adjacent_to_q.contains(v)); @@ -90,7 +93,7 @@ where }) .expect("Graph shouldn't be empty"); promising_candidates = candidates.iter().cloned().collect(); - let neighbors_u: HashSet = graph.neighbors(u).collect(); + let neighbors_u: HashSet = graph.neighbors(u).collect(); promising_candidates.retain(|v| !neighbors_u.contains(v)); } } @@ -111,7 +114,7 @@ where /// part of maximum cliques that are themselves bigger in size than k in arbitrary order. /// /// Uses the [find_maximum_cliques] method. -pub fn find_maximum_cliques_bounded( +pub fn find_maximum_cliques_bounded( graph: G, k: usize, ) -> impl Iterator @@ -123,9 +126,9 @@ where TargetColl: FromIterator, ::NodeId: 'static, { - let mut maximum_cliques = find_maximum_cliques::, G>(graph); - let mut combinations = HashSet::new().into_iter().combinations(k); - let mut seen_combinations = HashSet::new(); + let mut maximum_cliques = find_maximum_cliques::, G, S>(graph); + let mut combinations = crate::hashset![].into_iter().combinations(k); + let mut seen_combinations = crate::hashset![]; from_fn(move || loop { if let Some(mut clique_combination) = combinations.next() { clique_combination.sort(); @@ -147,6 +150,8 @@ where #[cfg(test)] mod tests { + use std::hash::RandomState; + use super::*; #[test] @@ -155,7 +160,7 @@ mod tests { let test_graph = crate::tests::setup_test_graph(i); let mut cliques: Vec> = - find_maximum_cliques::, _>(&test_graph.graph).collect(); + find_maximum_cliques::, _, RandomState>(&test_graph.graph).collect(); for i in 0..cliques.len() { cliques[i].sort(); @@ -175,7 +180,7 @@ mod tests { let test_graph = crate::tests::setup_test_graph(0); let mut cliques: Vec> = - find_maximum_cliques_bounded::, _>(&test_graph.graph, 3).collect(); + find_maximum_cliques_bounded::, _, RandomState>(&test_graph.graph, 3).collect(); for i in 0..cliques.len() { cliques[i].sort(); @@ -210,7 +215,7 @@ mod tests { let test_graph = crate::tests::setup_test_graph(2); let mut cliques: Vec> = - find_maximum_cliques_bounded::, _>(&test_graph.graph, 3).collect(); + find_maximum_cliques_bounded::, _, RandomState>(&test_graph.graph, 3).collect(); for i in 0..cliques.len() { cliques[i].sort(); diff --git a/treewidth_heuristic/src/find_path_in_tree.rs b/treewidth_heuristic/src/find_path_in_tree.rs index 8eb4a95..e0c3bd3 100644 --- a/treewidth_heuristic/src/find_path_in_tree.rs +++ b/treewidth_heuristic/src/find_path_in_tree.rs @@ -2,13 +2,17 @@ use log::warn; use petgraph::visit::IntoNeighborsDirected; use std::collections::{HashSet, VecDeque}; use std::fmt::Debug; -use std::hash::Hash; +use std::hash::{BuildHasher, Hash}; /// Returns an Option with a vector starting with start and continuing with a path to end, ending with end. /// Is implemented using a stack and depth first search. /// /// Returns None if no path exists (should never happen in a tree). -pub fn find_path_in_tree(graph: G, start: G::NodeId, end: G::NodeId) -> Option> +pub fn find_path_in_tree( + graph: G, + start: G::NodeId, + end: G::NodeId, +) -> Option> where T: FromIterator, G: IntoNeighborsDirected, @@ -19,7 +23,7 @@ where let mut stack = VecDeque::new(); stack.push_back((path_so_far.clone(), start)); - let mut visited = HashSet::new(); + let mut visited: HashSet<_, S> = Default::default(); while let Some((mut path_so_far, current_vertex)) = stack.pop_front() { for next_vertex in graph.neighbors(current_vertex) { diff --git a/treewidth_heuristic/src/find_width_of_tree_decomposition.rs b/treewidth_heuristic/src/find_width_of_tree_decomposition.rs index 94e32a6..d25ea8b 100644 --- a/treewidth_heuristic/src/find_width_of_tree_decomposition.rs +++ b/treewidth_heuristic/src/find_width_of_tree_decomposition.rs @@ -5,8 +5,8 @@ use std::collections::HashSet; /// This equals the highest len of one of the vertices in the graph. Returns 0 if the graph has no vertices /// /// Returns 0 if the graph is empty -pub fn find_width_of_tree_decomposition( - graph: &Graph, E, petgraph::prelude::Undirected>, +pub fn find_width_of_tree_decomposition( + graph: &Graph, E, petgraph::prelude::Undirected>, ) -> usize { if let Some(bag) = graph.node_weights().max_by_key(|b| b.len()) { bag.len() - 1 diff --git a/treewidth_heuristic/src/lib.rs b/treewidth_heuristic/src/lib.rs index fd51b05..64ca342 100644 --- a/treewidth_heuristic/src/lib.rs +++ b/treewidth_heuristic/src/lib.rs @@ -30,6 +30,25 @@ pub use generate_partial_k_tree::{ }; pub use maximum_minimum_degree_heuristic::maximum_minimum_degree; +// Debug version +#[cfg(debug_assertions)] +macro_rules! hashset { + () => {{ + let tmp: std::collections::HashSet<_, std::hash::BuildHasherDefault> = + Default::default(); + tmp + }}; +} + +// Non-debug version +#[cfg(not(debug_assertions))] +macro_rules! hashset { + () => { + std::collections::HashSet::new() + }; +} +pub(crate) use hashset; + #[cfg(test)] pub(crate) mod tests { use petgraph::{graph::NodeIndex, Graph}; @@ -259,4 +278,26 @@ pub(crate) mod tests { } } } + + #[test] + fn hash_test() { + let mut test = true; + for _ in 0..100 { + let a = (0..100).zip(100..200); + let mut set_one = hashset![]; + + let mut set_two = hashset![]; + + for entry in a { + set_one.insert(entry); + set_two.insert(entry); + } + + if !set_one.into_iter().eq(set_two) { + test = false; + } + } + + debug_assert!(test); + } }