diff --git a/dimacs_benchmarks/src/lib.rs b/dimacs_benchmarks/src/lib.rs index 6b0248e..a66fe17 100644 --- a/dimacs_benchmarks/src/lib.rs +++ b/dimacs_benchmarks/src/lib.rs @@ -6,20 +6,25 @@ pub enum HeuristicTypes { // FillWhile = Fill while building minimum spanning tree // Ni = Negative Intersection MstTreeNi, + FillWhileNi, // Ld = Least difference MstTreeLd, + FillWhileLd, // T = Then MstTreeNiTLd, FillWhileNiTLd, + FillWhileUpNiTLd, FillWhileTreeNiTLd, + MstTreeLdTNi, FillWhileLdTNi, // Bag = See [TreewidthComputationMethod::FillWhilstMSTBagSize] FillWhileBag, // BC = Bounded cliques MstTreeNiTLdBC(usize), + FillWhileNiTLdBC(usize), FillWhileTreeNiTLdBC(usize), } @@ -32,11 +37,13 @@ impl std::fmt::Display for HeuristicTypes { FillWhileLd => "FiWhLd".to_string(), MstTreeNiTLd => "MTrNiTLd".to_string(), FillWhileNiTLd => "FiWhNiTLd".to_string(), + FillWhileUpNiTLd => "FWUNiTLd".to_string(), MstTreeLdTNi => "MTrLdTNi".to_string(), FillWhileLdTNi => "FiWhLdTNi".to_string(), FillWhileTreeNiTLd => "FWTNiTLd".to_string(), FillWhileBag => "FWB".to_string(), MstTreeNiTLdBC(clique_bound) => format!("MTrNiTLdBC {}", clique_bound), + FillWhileNiTLdBC(clique_bound) => format!("FiWhLdTNiBC {}", clique_bound), FillWhileTreeNiTLdBC(clique_bound) => format!("FWTNiTLd {}", clique_bound), }; write!(f, "{}", display_string) @@ -53,7 +60,7 @@ use std::{collections::HashSet, hash::BuildHasher}; use petgraph::graph::NodeIndex; use HeuristicTypes::*; -pub const HEURISTICS_BEING_TESTED: [HeuristicTypes; 1] = [FillWhileNiTLd]; +pub const HEURISTICS_BEING_TESTED: [HeuristicTypes; 2] = [MstTreeNiTLd, MstTreeNiTLdBC(2)]; pub fn heuristic_to_edge_weight_heuristic( heuristic: &HeuristicTypes, @@ -77,6 +84,9 @@ pub fn heuristic_to_edge_weight_heuristic( FillWhileNiTLd => { EdgeWeightTypes::ReturnI32Tuple(negative_intersection_then_least_difference_heuristic) } + FillWhileUpNiTLd => { + EdgeWeightTypes::ReturnI32Tuple(negative_intersection_then_least_difference_heuristic) + } FillWhileTreeNiTLd => { EdgeWeightTypes::ReturnI32Tuple(negative_intersection_then_least_difference_heuristic) } @@ -86,6 +96,9 @@ pub fn heuristic_to_edge_weight_heuristic( MstTreeNiTLdBC(_) => { EdgeWeightTypes::ReturnI32Tuple(negative_intersection_then_least_difference_heuristic) } + FillWhileNiTLdBC(_) => { + EdgeWeightTypes::ReturnI32Tuple(negative_intersection_then_least_difference_heuristic) + } FillWhileTreeNiTLdBC(_) => { EdgeWeightTypes::ReturnI32Tuple(negative_intersection_then_least_difference_heuristic) } @@ -104,9 +117,11 @@ pub fn heuristic_to_computation_type( FillWhileLdTNi => FillWhilstMST, MstTreeNiTLd => MSTAndUseTreeStructure, FillWhileNiTLd => FillWhilstMST, + FillWhileUpNiTLd => FillWhilstMSTEdgeUpdate, FillWhileTreeNiTLd => FillWhilstMSTTree, FillWhileBag => FillWhilstMSTBagSize, MstTreeNiTLdBC(_) => MSTAndUseTreeStructure, + FillWhileNiTLdBC(_) => FillWhilstMST, FillWhileTreeNiTLdBC(_) => FillWhilstMSTTree, } } @@ -121,9 +136,11 @@ pub fn heuristic_to_clique_bound(heuristic: &HeuristicTypes) -> Option { FillWhileLdTNi => None, MstTreeNiTLd => None, FillWhileNiTLd => None, + FillWhileUpNiTLd => None, FillWhileTreeNiTLd => None, FillWhileBag => None, MstTreeNiTLdBC(clique_bound) => Some(*clique_bound), + FillWhileNiTLdBC(clique_bound) => Some(*clique_bound), FillWhileTreeNiTLdBC(clique_bound) => Some(*clique_bound), } } diff --git a/dimacs_benchmarks/src/main.rs b/dimacs_benchmarks/src/main.rs index 3964c9f..a40e53a 100644 --- a/dimacs_benchmarks/src/main.rs +++ b/dimacs_benchmarks/src/main.rs @@ -18,7 +18,7 @@ type Hasher = std::hash::RandomState; fn main() { env_logger::init(); - let number_of_repetitions_per_heuristic = 1; + let number_of_repetitions_per_heuristic = 5; let mut benchmark_log_file = File::create("dimacs_benchmarks/benchmark_results/dimacs_results.txt") @@ -81,7 +81,7 @@ fn main() { &graph, edge_weight_heuristic, computation_type, - true, + false, clique_bound, ) } @@ -90,7 +90,7 @@ fn main() { &graph, edge_weight_heuristic, computation_type, - true, + false, clique_bound, ) } diff --git a/treewidth_heuristic/src/check_tree_decomposition.rs b/treewidth_heuristic/src/check_tree_decomposition.rs index d0a20b9..ece0b0c 100644 --- a/treewidth_heuristic/src/check_tree_decomposition.rs +++ b/treewidth_heuristic/src/check_tree_decomposition.rs @@ -35,7 +35,6 @@ pub fn check_tree_decomposition( return false; } } - // Check if (2) from tree decomposition is satisfied (for all edges in starting graph there is bag containing // both its vertices) for edge_reference in starting_graph.edge_references() { @@ -56,7 +55,6 @@ pub fn check_tree_decomposition( return false; } } - // check if (3) from tree decomposition definition is satisfied (for one vertex in starting graph, all bags // contain this vertex induce a subtree) for mut vec in tree_decomposition_graph.node_references().combinations(2) { diff --git a/treewidth_heuristic/src/compute_treewidth_upper_bound.rs b/treewidth_heuristic/src/compute_treewidth_upper_bound.rs index 60649a7..0d66ba6 100644 --- a/treewidth_heuristic/src/compute_treewidth_upper_bound.rs +++ b/treewidth_heuristic/src/compute_treewidth_upper_bound.rs @@ -11,8 +11,11 @@ use petgraph::{graph::NodeIndex, Graph, Undirected}; pub enum TreewidthComputationMethod { MSTAndFill, MSTAndUseTreeStructure, + // Fill bags while constructing a spanning tree minimizing according to the edge heuristic FillWhilstMST, - // Fills bags while constructing a spanning tree of the clique graph trying to minimize the maximum bag size in each step + // Fill bags while constructing a spanning tree minimizing according to the edge heuristic. Updating adjacencies in clique graph according to bag updates + FillWhilstMSTEdgeUpdate, + // Fill bags while constructing a spanning tree minimizing according to the edge heuristic trying to speed up by using the tree structure FillWhilstMSTTree, // Fills bags while constructing a spanning tree of the clique graph trying to minimize the maximum bag size in each step FillWhilstMSTBagSize, @@ -56,6 +59,9 @@ pub fn compute_treewidth_upper_bound< // .sorted() .collect() }; + if cliques.len() > 3 { + println!("Number of cliques: {}", cliques.len()); + } let ( clique_graph_tree_after_filling_up, @@ -119,11 +125,6 @@ pub fn compute_treewidth_upper_bound< let predecessor_map = fill_bags_along_paths_using_structure(&mut clique_graph_tree, &clique_graph_map); - // DEBUG - // println!( - // "Clique graph tree after filling up: {:?} \n \n", - // clique_graph_tree - // ); ( clique_graph_tree, @@ -149,6 +150,22 @@ pub fn compute_treewidth_upper_bound< (clique_graph_tree, None, None, None, clique_graph) } + TreewidthComputationMethod::FillWhilstMSTEdgeUpdate => { + let (clique_graph, clique_graph_map) = + construct_clique_graph_with_bags(cliques, edge_weight_heuristic); + + let clique_graph_tree: Graph< + std::collections::HashSet, + O, + petgraph::prelude::Undirected, + > = fill_bags_while_generating_mst_update_edges::( + &clique_graph, + edge_weight_heuristic, + clique_graph_map, + ); + + (clique_graph_tree, None, None, None, clique_graph) + } TreewidthComputationMethod::FillWhilstMSTTree => { let (clique_graph, clique_graph_map) = construct_clique_graph_with_bags(cliques, edge_weight_heuristic); diff --git a/treewidth_heuristic/src/fill_bags_along_paths.rs b/treewidth_heuristic/src/fill_bags_along_paths.rs index a84a64a..21fb219 100644 --- a/treewidth_heuristic/src/fill_bags_along_paths.rs +++ b/treewidth_heuristic/src/fill_bags_along_paths.rs @@ -108,7 +108,6 @@ pub fn fill_bags_along_paths_using_structure( } } +pub fn fill_bags_while_generating_mst_update_edges< + N, + E, + O: Ord, + S: Default + BuildHasher + Clone, +>( + clique_graph: &Graph, O, Undirected>, + edge_weight_heuristic: fn(&HashSet, &HashSet) -> O, + clique_graph_map: HashMap, S>, +) -> Graph, O, Undirected> { + let mut result_graph: Graph, O, 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 = 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(); + + // 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), S> = Default::default(); + + let first_vertex_res = result_graph.add_node( + clique_graph + .node_weight(first_vertex_clique) + .expect("Vertices in clique graph should have bags as weights") + .clone(), + ); + + // Add vertices that are reachable from first vertex + for neighbor in clique_graph.neighbors(first_vertex_clique) { + currently_interesting_vertices.insert((first_vertex_res, neighbor)); + } + node_index_map.insert(first_vertex_clique, first_vertex_res); + + while !clique_graph_remaining_vertices.is_empty() { + // The cheapest_old_vertex_res is one of the vertices from the already constructed tree that the new vertex + // is being attached to + // The cheapest_new_vertex_clique is the new vertex that is being added to the tree. The NodeIndex corresponds + // to the vertex in the clique graph and not the result graph and thus still needs to be translated. + let (cheapest_old_vertex_res, cheapest_new_vertex_clique) = find_cheapest_vertex( + &clique_graph, + &result_graph, + edge_weight_heuristic, + ¤tly_interesting_vertices, + ); + clique_graph_remaining_vertices.remove(&cheapest_new_vertex_clique); + + // Update result graph + let cheapest_new_vertex_res = result_graph.add_node( + clique_graph + .node_weight(cheapest_new_vertex_clique) + .expect("Vertices in clique graph should have bags as weights") + .clone(), + ); + + node_index_map.insert(cheapest_new_vertex_clique, cheapest_new_vertex_res); + result_graph.add_edge( + cheapest_old_vertex_res, + cheapest_new_vertex_res, + edge_weight_heuristic( + result_graph + .node_weight(cheapest_old_vertex_res) + .expect("Vertices should have bags as weight"), + result_graph + .node_weight(cheapest_new_vertex_res) + .expect("Vertices should have bags as weight"), + ), + ); + + // Update currently interesting vertices + for neighbor in clique_graph.neighbors(cheapest_new_vertex_clique) { + if clique_graph_remaining_vertices.contains(&neighbor) { + currently_interesting_vertices.insert((cheapest_new_vertex_res, neighbor)); + } + } + + currently_interesting_vertices + .retain(|(_, vertex_clique)| !vertex_clique.eq(&cheapest_new_vertex_clique)); + + fill_bags_from_result_graph_updating_edges( + &mut result_graph, + cheapest_new_vertex_res, + cheapest_old_vertex_res, + &clique_graph_map, + &node_index_map, + &mut currently_interesting_vertices, + ); + } + + result_graph +} + +fn fill_bags_from_result_graph_updating_edges( + result_graph: &mut Graph, O, Undirected>, + new_vertex_res: NodeIndex, + cheapest_old_vertex_res: NodeIndex, + clique_graph_map: &HashMap, S>, + node_index_map: &HashMap, + currently_interesting_vertices: &mut HashSet<(NodeIndex, NodeIndex), S>, +) { + for vertex_from_starting_graph in result_graph + .node_weight(new_vertex_res) + .expect("Vertex should have weight since it was just added") + .clone() + .difference( + &result_graph + .node_weight(cheapest_old_vertex_res) + .expect("Vertex should have bag as weight") + .clone(), + ) + { + if let Some(vertices_in_clique_graph) = clique_graph_map.get(&vertex_from_starting_graph) { + for vertex_in_clique_graph in vertices_in_clique_graph { + if let Some(vertex_res_graph) = node_index_map.get(vertex_in_clique_graph) { + if vertex_res_graph != &new_vertex_res { + fill_bags_updating_edges( + new_vertex_res, + *vertex_res_graph, + result_graph, + *vertex_from_starting_graph, + clique_graph_map, + node_index_map, + currently_interesting_vertices, + ); + } + } + } + } + } +} + +/// Adapted from fill_bags +fn fill_bags_updating_edges( + start_vertex: NodeIndex, + end_vertex: NodeIndex, + graph: &mut Graph, O, Undirected>, + vertex_to_be_insert_from_starting_graph: NodeIndex, + clique_graph_map: &HashMap, S>, + node_index_map: &HashMap, + currently_interesting_vertices: &mut HashSet<(NodeIndex, NodeIndex), S>, +) { + let mut path: Vec<_> = petgraph::algo::simple_paths::all_simple_paths::, _>( + &*graph, + start_vertex, + end_vertex, + 0, + None, + ) + .next() + .expect("There should be a path in the tree"); + + // Last element is the given end node + path.pop(); + + for node_index in path { + if node_index != start_vertex { + graph + .node_weight_mut(node_index) + .expect("Bag for the vertex should exist") + .insert(vertex_to_be_insert_from_starting_graph); + + for vertex_clique_graph in clique_graph_map + .get(&vertex_to_be_insert_from_starting_graph) + .expect("There should be bags containing this vertex") + { + if !node_index_map.contains_key(vertex_clique_graph) { + currently_interesting_vertices.insert((node_index, *vertex_clique_graph)); + } + } + } + } +} + /// Finds the cheapest edge to a vertex not yet in the result graph considering the bags in the result graph /// /// Returns a tuple with a node index from the result graph in the first and node index from the clique graph diff --git a/treewidth_heuristic/src/lib.rs b/treewidth_heuristic/src/lib.rs index ab2fa06..d36f2c3 100644 --- a/treewidth_heuristic/src/lib.rs +++ b/treewidth_heuristic/src/lib.rs @@ -24,7 +24,7 @@ pub(crate) use fill_bags_along_paths::{ }; pub(crate) use fill_bags_while_generating_mst::{ fill_bags_while_generating_mst, fill_bags_while_generating_mst_least_bag_size, - fill_bags_while_generating_mst_using_tree, + fill_bags_while_generating_mst_update_edges, fill_bags_while_generating_mst_using_tree, }; pub(crate) use find_connected_components::find_connected_components; pub(crate) use find_maximum_cliques::{find_maximum_cliques, find_maximum_cliques_bounded};