diff --git a/.gitignore b/.gitignore index df5ef96..9c2e769 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target /test_results /dimacs_benchmarks/benchmark_results -/k_tree_benchmarks/benchmark_results \ No newline at end of file +/k_tree_benchmarks/benchmark_results +k_tree_benchmarks/benchmark_results/k_tree_results.txt diff --git a/k_tree_benchmarks/benchmark_results/k_tree_results.txt b/k_tree_benchmarks/benchmark_results/k_tree_results.txt index e69de29..18f6535 100644 --- a/k_tree_benchmarks/benchmark_results/k_tree_results.txt +++ b/k_tree_benchmarks/benchmark_results/k_tree_results.txt @@ -0,0 +1,100 @@ +Graph 0: 6 6 took 0 milliseconds +Graph 1: 6 6 took 0 milliseconds +Graph 2: 6 6 took 0 milliseconds +Graph 3: 6 6 took 0 milliseconds +Graph 4: 7 8 took 1 milliseconds +Graph 5: 6 6 took 0 milliseconds +Graph 6: 5 5 took 0 milliseconds +Graph 7: 6 6 took 0 milliseconds +Graph 8: 8 7 took 1 milliseconds +Graph 9: 10 8 took 1 milliseconds +Graph 10: 8 8 took 1 milliseconds +Graph 11: 6 7 took 1 milliseconds +Graph 12: 5 6 took 0 milliseconds +Graph 13: 6 6 took 1 milliseconds +Graph 14: 7 8 took 0 milliseconds +Graph 15: 8 7 took 0 milliseconds +Graph 16: 5 6 took 0 milliseconds +Graph 17: 6 6 took 1 milliseconds +Graph 18: 8 8 took 0 milliseconds +Graph 19: 7 6 took 1 milliseconds +Graph 20: 6 7 took 1 milliseconds +Graph 21: 6 6 took 0 milliseconds +Graph 22: 7 8 took 0 milliseconds +Graph 23: 6 6 took 0 milliseconds +Graph 24: 10 10 took 1 milliseconds +Graph 25: 7 7 took 1 milliseconds +Graph 26: 6 6 took 0 milliseconds +Graph 27: 6 6 took 0 milliseconds +Graph 28: 6 6 took 0 milliseconds +Graph 29: 8 8 took 2 milliseconds +Graph 30: 6 6 took 0 milliseconds +Graph 31: 6 6 took 1 milliseconds +Graph 32: 6 6 took 1 milliseconds +Graph 33: 5 6 took 0 milliseconds +Graph 34: 8 7 took 2 milliseconds +Graph 35: 9 6 took 1 milliseconds +Graph 36: 7 6 took 1 milliseconds +Graph 37: 7 8 took 0 milliseconds +Graph 38: 6 7 took 0 milliseconds +Graph 39: 6 6 took 0 milliseconds +Graph 40: 7 6 took 1 milliseconds +Graph 41: 6 7 took 1 milliseconds +Graph 42: 6 7 took 1 milliseconds +Graph 43: 6 7 took 0 milliseconds +Graph 44: 6 6 took 0 milliseconds +Graph 45: 8 7 took 2 milliseconds +Graph 46: 8 6 took 1 milliseconds +Graph 47: 7 7 took 0 milliseconds +Graph 48: 8 7 took 0 milliseconds +Graph 49: 7 8 took 0 milliseconds +Graph 50: 6 7 took 1 milliseconds +Graph 51: 6 7 took 1 milliseconds +Graph 52: 7 7 took 1 milliseconds +Graph 53: 5 6 took 0 milliseconds +Graph 54: 5 5 took 0 milliseconds +Graph 55: 6 5 took 0 milliseconds +Graph 56: 6 6 took 1 milliseconds +Graph 57: 6 7 took 0 milliseconds +Graph 58: 6 7 took 0 milliseconds +Graph 59: 7 6 took 0 milliseconds +Graph 60: 6 7 took 0 milliseconds +Graph 61: 6 6 took 0 milliseconds +Graph 62: 8 7 took 1 milliseconds +Graph 63: 8 9 took 1 milliseconds +Graph 64: 6 6 took 0 milliseconds +Graph 65: 6 7 took 1 milliseconds +Graph 66: 7 6 took 1 milliseconds +Graph 67: 6 7 took 1 milliseconds +Graph 68: 6 6 took 0 milliseconds +Graph 69: 8 8 took 1 milliseconds +Graph 70: 8 9 took 1 milliseconds +Graph 71: 7 7 took 0 milliseconds +Graph 72: 5 5 took 0 milliseconds +Graph 73: 6 7 took 0 milliseconds +Graph 74: 6 6 took 0 milliseconds +Graph 75: 6 6 took 1 milliseconds +Graph 76: 6 6 took 0 milliseconds +Graph 77: 7 7 took 1 milliseconds +Graph 78: 8 9 took 2 milliseconds +Graph 79: 5 6 took 0 milliseconds +Graph 80: 6 6 took 0 milliseconds +Graph 81: 8 8 took 1 milliseconds +Graph 82: 6 6 took 0 milliseconds +Graph 83: 8 7 took 1 milliseconds +Graph 84: 7 6 took 0 milliseconds +Graph 85: 8 9 took 1 milliseconds +Graph 86: 6 5 took 0 milliseconds +Graph 87: 5 6 took 0 milliseconds +Graph 88: 9 9 took 1 milliseconds +Graph 89: 6 6 took 0 milliseconds +Graph 90: 7 6 took 0 milliseconds +Graph 91: 6 8 took 0 milliseconds +Graph 92: 9 6 took 1 milliseconds +Graph 93: 8 6 took 1 milliseconds +Graph 94: 8 8 took 3 milliseconds +Graph 95: 8 7 took 1 milliseconds +Graph 96: 7 6 took 0 milliseconds +Graph 97: 6 8 took 1 milliseconds +Graph 98: 6 6 took 0 milliseconds +Graph 99: 5 5 took 0 milliseconds diff --git a/k_tree_benchmarks/src/main.rs b/k_tree_benchmarks/src/main.rs index ecb9402..1e52801 100644 --- a/k_tree_benchmarks/src/main.rs +++ b/k_tree_benchmarks/src/main.rs @@ -1,14 +1,18 @@ -use std::fs::File; +use std::collections::HashSet; +use std::fs::{self, File}; use std::io::Write; +use petgraph::dot::{Config, Dot}; +use petgraph::graph::NodeIndex; use petgraph::Graph; use std::time::SystemTime; -use treewidth_heuristic::compute_treewidth_upper_bound_not_connected; +use treewidth_heuristic::compute_treewidth_upper_bound; fn main() { - let k = 20; - let n = 300; - let p = 0; + let k = 5; + let n = 15; + let p = 10; + let edge_heuristic = treewidth_heuristic::least_difference_heuristic; // Opening and writing to log file let mut benchmark_log_file = @@ -23,24 +27,34 @@ fn main() { p, &mut rand::thread_rng(), ) - .expect("n is greater than k"); + .expect("n should be greater than k"); println!("Starting calculation on graph number: {:?}", i); // Time the calculation let start = SystemTime::now(); - let computed_treewidth = compute_treewidth_upper_bound_not_connected( - &graph, - treewidth_heuristic::negative_intersection_heuristic, - true, - true, - ); + let ( + clique_graph, + clique_graph_before_filling_up, + predecessor_map, + clique_graph_map, + computed_treewidth, + ) = compute_treewidth_upper_bound(&graph, edge_heuristic, true, true); + + let ( + clique_graph_alt, + clique_graph_before_filling_up_alt, + predecessor_map_altp, + clique_graph_map_alt, + computed_treewidth_alt, + ) = compute_treewidth_upper_bound(&graph, edge_heuristic, false, true); benchmark_log_file .write_all( format!( - "Graph {:?}: {} took {:.3} milliseconds\n", + "Graph {:?}: {} {} took {:.3} milliseconds\n", i, computed_treewidth, + computed_treewidth_alt, start .elapsed() .expect("Time should be trackable") @@ -49,5 +63,85 @@ fn main() { .as_bytes(), ) .expect("Writing to Dimacs log file should be possible"); + + // Debug + // if i == 0 { + // println!( + // "Predecessor map first graph: \n {:?}", + // predecessor_map.unwrap() + // ); + // println!( + // "Clique graph map first graph: \n {:?}", + // clique_graph_map.unwrap() + // ); + // } + + create_dot_files( + &graph, + &clique_graph, + &clique_graph_before_filling_up, + i, + "predecessors", + ); + create_dot_files( + &graph, + &clique_graph_alt, + &clique_graph_before_filling_up_alt, + i, + "no_predecessors", + ); } } + +// Converting dot files to pdf in bulk: +// FullPath -type f -name "*.dot" | xargs dot -Tpdf -O +fn create_dot_files( + graph: &Graph, + clique_graph: &Graph, i32, petgraph::prelude::Undirected>, + clique_graph_before_filling_up: &Graph, i32, petgraph::prelude::Undirected>, + i: usize, + name: &str, +) { + let start_graph_dot_file = Dot::with_config(graph, &[Config::EdgeNoLabel]); + let result_graph_dot_file = Dot::with_config(clique_graph, &[Config::EdgeNoLabel]); + let clique_graph_before_filling_up_dot_file = + Dot::with_config(&clique_graph_before_filling_up, &[Config::EdgeNoLabel]); + let clique_graph_node_indices = Dot::with_config( + &clique_graph_before_filling_up, + &[Config::EdgeNoLabel, Config::NodeIndexLabel], + ); + + fs::create_dir_all("k_tree_benchmarks/benchmark_results/visualizations") + .expect("Could not create directory for visualizations"); + + let mut w = fs::File::create(format!( + "k_tree_benchmarks/benchmark_results/visualizations/{}_starting_graph_{}.dot", + i, name + )) + .expect("Start graph file could not be created"); + write!(&mut w, "{:?}", start_graph_dot_file) + .expect("Unable to write dotfile for start graph to files"); + let mut w = fs::File::create(format!( + "k_tree_benchmarks/benchmark_results/visualizations/{}_result_graph_before_filling_{}.dot", + i, name + )) + .expect("Result graph without filling up file could not be created"); + write!(&mut w, "{:?}", clique_graph_before_filling_up_dot_file) + .expect("Unable to write dotfile for result graph without filling up to files"); + + let mut w = fs::File::create(format!( + "k_tree_benchmarks/benchmark_results/visualizations/{}_result_graph_node_indices_{}.dot", + i, name + )) + .expect("Clique graph node indices file could not be created"); + write!(&mut w, "{:?}", clique_graph_node_indices) + .expect("Unable to write dotfile for Clique graph node indices to files"); + + let mut w = fs::File::create(format!( + "k_tree_benchmarks/benchmark_results/visualizations/{}_result_graph_{}.dot", + i, name + )) + .expect("Result graph file could not be created"); + write!(&mut w, "{:?}", result_graph_dot_file) + .expect("Unable to write dotfile for result graph to files"); +} diff --git a/treewidth_heuristic/src/check_tree_decomposition.rs b/treewidth_heuristic/src/check_tree_decomposition.rs index fa377bb..b323990 100644 --- a/treewidth_heuristic/src/check_tree_decomposition.rs +++ b/treewidth_heuristic/src/check_tree_decomposition.rs @@ -17,8 +17,8 @@ pub fn check_tree_decomposition( i32, petgraph::prelude::Undirected, >, - predecessor_map: Option>, - clique_graph_map: Option>>, + predecessor_map: &Option>, + clique_graph_map: &Option>>, ) -> bool { for mut vec in tree_decomposition_graph.node_references().combinations(2) { let first_tuple = vec.pop().expect("Vec should contain two items"); diff --git a/treewidth_heuristic/src/clique_graph_edge_weight_heuristics.rs b/treewidth_heuristic/src/clique_graph_edge_weight_heuristics.rs index 761a004..ea65afd 100644 --- a/treewidth_heuristic/src/clique_graph_edge_weight_heuristics.rs +++ b/treewidth_heuristic/src/clique_graph_edge_weight_heuristics.rs @@ -6,6 +6,7 @@ pub fn neutral_heuristic(_: &HashSet, _: &HashSet) -> i32 0 } +// Classic pub fn negative_intersection_heuristic( first_vertex: &HashSet, second_vertex: &HashSet, @@ -42,3 +43,14 @@ pub fn union_heuristic( .collect::>() .len() as i32 } + +// Classic alt? +pub fn least_difference_heuristic( + first_vertex: &HashSet, + second_vertex: &HashSet, +) -> i32 { + first_vertex + .symmetric_difference(second_vertex) + .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 f3d4065..a711a60 100644 --- a/treewidth_heuristic/src/compute_treewidth_upper_bound.rs +++ b/treewidth_heuristic/src/compute_treewidth_upper_bound.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use crate::*; use petgraph::{graph::NodeIndex, Graph, Undirected}; @@ -17,62 +17,84 @@ pub fn compute_treewidth_upper_bound( edge_weight_heuristic: fn(&HashSet, &HashSet) -> i32, use_predecessor_map_to_fill_bags: bool, check_tree_decomposition_bool: bool, -) -> usize { +) -> ( + Graph, i32, Undirected>, + Graph, i32, Undirected>, + Option>, + Option>>, + usize, +) { // Find cliques in initial graph let cliques: Vec> = find_maximum_cliques::, _>(graph).collect(); - let (clique_graph_tree, clique_graph_map, predecessor_map) = if use_predecessor_map_to_fill_bags - { - let (clique_graph, clique_graph_map) = - construct_clique_graph_with_bags(cliques, edge_weight_heuristic); - // DEBUG - // println!("Initial clique graph: {:?}", clique_graph); - - let mut clique_graph_tree: Graph< - std::collections::HashSet, - i32, - petgraph::prelude::Undirected, - > = petgraph::data::FromElements::from_elements(petgraph::algo::min_spanning_tree( - &clique_graph, - )); - - let predecessor_map = - fill_bags_along_paths_abusing_structure(&mut clique_graph_tree, &clique_graph_map); - // DEBUG - // println!( - // "Clique graph tree after filling up: {:?} \n \n", - // clique_graph_tree - // ); - - ( - clique_graph_tree, - Some(clique_graph_map), - Some(predecessor_map), - ) - } else { - let clique_graph: Graph<_, _, _> = construct_clique_graph(cliques, edge_weight_heuristic); - - let mut clique_graph_tree: Graph< - std::collections::HashSet, - i32, - petgraph::prelude::Undirected, - > = petgraph::data::FromElements::from_elements(petgraph::algo::min_spanning_tree( - &clique_graph, - )); - - fill_bags_along_paths(&mut clique_graph_tree); - - (clique_graph_tree, None, None) - }; + let (clique_graph_tree, clique_graph_map, predecessor_map, clique_graph_tree_before_filling) = + if use_predecessor_map_to_fill_bags { + let (clique_graph, clique_graph_map) = + construct_clique_graph_with_bags(cliques, edge_weight_heuristic); + // DEBUG + // println!("Initial clique graph: {:?}", clique_graph); + + let mut clique_graph_tree: Graph< + std::collections::HashSet, + i32, + petgraph::prelude::Undirected, + > = petgraph::data::FromElements::from_elements(petgraph::algo::min_spanning_tree( + &clique_graph, + )); + let clique_graph_tree_before_filling = clique_graph_tree.clone(); + + let predecessor_map = + fill_bags_along_paths_abusing_structure(&mut clique_graph_tree, &clique_graph_map); + // DEBUG + // println!( + // "Clique graph tree after filling up: {:?} \n \n", + // clique_graph_tree + // ); + + ( + clique_graph_tree, + Some(clique_graph_map), + Some(predecessor_map), + clique_graph_tree_before_filling, + ) + } else { + let clique_graph: Graph<_, _, _> = + construct_clique_graph(cliques, edge_weight_heuristic); + + let mut clique_graph_tree: Graph< + std::collections::HashSet, + i32, + petgraph::prelude::Undirected, + > = petgraph::data::FromElements::from_elements(petgraph::algo::min_spanning_tree( + &clique_graph, + )); + let clique_graph_tree_before_filling = clique_graph_tree.clone(); + + fill_bags_along_paths(&mut clique_graph_tree); + + ( + clique_graph_tree, + None, + None, + clique_graph_tree_before_filling, + ) + }; if check_tree_decomposition_bool { assert!(check_tree_decomposition( &clique_graph_tree, - predecessor_map, - clique_graph_map + &predecessor_map, + &clique_graph_map )); } - - find_width_of_tree_decomposition(&clique_graph_tree) + let treewidth = find_width_of_tree_decomposition(&clique_graph_tree); + + ( + clique_graph_tree, + clique_graph_tree_before_filling, + predecessor_map, + clique_graph_map, + treewidth, + ) } /// Computes an upper bound for the treewidth returning the maximum [compute_treewidth_upper_bound] on the @@ -90,12 +112,15 @@ pub fn compute_treewidth_upper_bound_not_connected( let mut subgraph = graph.clone(); subgraph.retain_nodes(|_, v| component.contains(&v)); - computed_treewidth = computed_treewidth.max(compute_treewidth_upper_bound( - &subgraph, - edge_weight_heuristic, - use_predecessor_map_to_fill_bags, - check_tree_decomposition_bool, - )); + computed_treewidth = computed_treewidth.max( + compute_treewidth_upper_bound( + &subgraph, + edge_weight_heuristic, + use_predecessor_map_to_fill_bags, + check_tree_decomposition_bool, + ) + .4, + ); } computed_treewidth diff --git a/treewidth_heuristic/src/fill_bags_along_paths.rs b/treewidth_heuristic/src/fill_bags_along_paths.rs index fac53b0..ece8687 100644 --- a/treewidth_heuristic/src/fill_bags_along_paths.rs +++ b/treewidth_heuristic/src/fill_bags_along_paths.rs @@ -85,7 +85,7 @@ pub fn fill_bags_along_paths( /// 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_abusing_structure( graph: &mut Graph, E, petgraph::prelude::Undirected>, - map: &HashMap>, + clique_graph_map: &HashMap>, ) -> HashMap { info!("Building tree structure"); @@ -106,13 +106,14 @@ pub fn fill_bags_along_paths_abusing_structure( tree_predecessor_map ); - for vertex_in_initial_graph in map.keys() { + for vertex_in_initial_graph in clique_graph_map.keys() { info!("Filling up bags"); fill_bags_until_common_predecessor( graph, &tree_predecessor_map, &vertex_in_initial_graph, - &map.get(vertex_in_initial_graph) + &clique_graph_map + .get(vertex_in_initial_graph) .expect("key should exist by loop invariant"), ) } @@ -165,14 +166,18 @@ fn fill_bags_until_common_predecessor( vertex_in_initial_graph: &NodeIndex, vertices_in_clique_graph: &HashSet, ) { + // TO DO ? + // Maybe optimize by not filling up vertices_in_clique_graph, but inserting their predecessors already + // NOTE: Keep in mind, that one of the vertices_in_clique_graph might be the greatest common ancestor, + // so this can be done for all vertices_in_clique_graph that don't have the minimizing level (possible implementation) let mut predecessors: BTreeSet = BTreeSet::new(); if vertices_in_clique_graph.len() > 1 { for vertex_in_clique_graph in vertices_in_clique_graph { - if let Some((predecessor, index)) = predecessors_map.get(vertex_in_clique_graph) { + if let Some((_, index)) = predecessors_map.get(vertex_in_clique_graph) { // Skip the vertex in clique graph since it already contains the vertex in initial graph predecessors.insert(Predecessor { - node_index: *predecessor, - level_index: *index, + node_index: *vertex_in_clique_graph, + level_index: *index + 1, }); } else { // If there is no predecessor, vertex_in_clique_graph is the root node and as such diff --git a/treewidth_heuristic/src/generate_partial_k_tree.rs b/treewidth_heuristic/src/generate_partial_k_tree.rs index 848757a..c588661 100644 --- a/treewidth_heuristic/src/generate_partial_k_tree.rs +++ b/treewidth_heuristic/src/generate_partial_k_tree.rs @@ -75,8 +75,8 @@ fn generate_k_tree(k: usize, n: usize) -> Option> { let mut potential_cliques: Vec> = vec![graph.node_identifiers().collect()]; // Add the missing n-k vertices - for _ in k..n { - let new_vertex = graph.add_node(0); + for i in k..n { + let new_vertex = graph.add_node(i.try_into().unwrap()); let chosen_k_clique = potential_cliques .choose(&mut rand::thread_rng()) .expect("There should be potential cliques") @@ -101,7 +101,9 @@ fn generate_complete_graph(k: usize) -> Graph { petgraph::Graph::new_undirected(); // Add k nodes to the graph - let nodes: Vec = (0..k).map(|_| graph.add_node(0)).collect(); + let nodes: Vec = (0..k) + .map(|i| graph.add_node(i.try_into().unwrap())) + .collect(); // Connect each node to every other node for i in 0..k {