Hypergraph FKAlgorithmA::transversal (const Hypergraph& H) const { BOOST_LOG_TRIVIAL(debug) << "Starting FKA. Hypergraph has " << H.num_verts() << " vertices and " << H.num_edges() << " edges."; Hypergraph G (H.num_verts()); Hypergraph Hmin = H.minimization(); Hypergraph::Edge V = Hmin.verts_covered(); bool still_searching_for_transversals = true; while (still_searching_for_transversals) { Hypergraph::Edge omit_set = find_omit_set(Hmin, G); if (omit_set.none() and G.num_edges() > 0) { BOOST_LOG_TRIVIAL(debug) << "Received empty omit_set, so we're done."; still_searching_for_transversals = false; } else { Hypergraph::Edge new_hs = V - omit_set; Hypergraph::Edge new_mhs = minimize_new_hs(H, G, new_hs); BOOST_LOG_TRIVIAL(trace) << "Received witness." << "\nomit_set:\t" << omit_set << "\nMHS:\t\t" << new_mhs; G.add_edge(new_mhs, true); BOOST_LOG_TRIVIAL(debug) << "New G size: " << G.num_edges(); } } return G; }
Hypergraph ParBMAlgorithm::transversal (const Hypergraph& H) const { BOOST_LOG_TRIVIAL(debug) << "Starting BM with " << num_threads << " threads. Hypergraph has " << H.num_verts() << " vertices and " << H.num_edges() << " edges."; // Set up inputs Hypergraph Hmin = H.minimization(); Hypergraph G (H.num_verts()); Hypergraph::Edge V = Hmin.verts_covered(); // Initialize using any HS we can find Hypergraph::Edge first_hs = FKAlgorithm::minimize_new_hs(Hmin, G, V); G.add_edge(first_hs); // Grow G until it covers all vertices bool G_has_full_coverage = false; while (not G_has_full_coverage) { Hypergraph::Edge new_hs = V - coverage_condition_check(H, G); if (new_hs.is_proper_subset_of(V)) { Hypergraph::Edge new_mhs = FKAlgorithm::minimize_new_hs(Hmin, G, new_hs); G.add_edge(new_mhs); } else { G_has_full_coverage = true; } } // Apply the BM algorithm repeatedly, generating new transversals // until duality is confirmed bool still_searching_for_transversals = true; Hypergraph::EdgeQueue new_hses, new_mhses; #pragma omp parallel shared(Hmin, G, new_hses, new_mhses) num_threads(num_threads) #pragma omp master while (still_searching_for_transversals) { find_new_hses(Hmin, G, Hmin.verts_covered(), new_hses); if (new_hses.size_approx() == 0) { still_searching_for_transversals = false; } else { minimize_new_hses(Hmin, G, new_hses, new_mhses); Hypergraph::Edge new_mhs; while (new_mhses.try_dequeue(new_mhs)) { // The results will all be inclusion-minimal, but // there may be some overlap. Thus, we try to add // them... try { G.add_edge(new_mhs, true); } // But ignore any minimality_violated_exception // that is thrown. catch (minimality_violated_exception& e) {} } BOOST_LOG_TRIVIAL(debug) << "New |G|: " << G.num_edges(); } } return G; }
Hypergraph::Edge FKAlgorithmA::find_omit_set (const Hypergraph& F, const Hypergraph& G) { /** Test whether F and G are dual. If so, return an empty edge as a notification. If not, return a omit_set as per FK. **/ BOOST_LOG_TRIVIAL(trace) << "Beginning run with " << "|F| = " << F.num_edges() << " and |G| = " << G.num_edges(); // Input specification assert(F.num_verts() == G.num_verts()); // Create an empty omit_set to use as temporary storage Hypergraph::Edge omit_set (F.num_verts()); // FK step 1: initialize if G is empty if (G.num_edges() == 0) { BOOST_LOG_TRIVIAL(trace) << "Returning empty omit_set since G is empty."; return omit_set; } // FK step 2: consistency checks // Check 1.1: hitting condition omit_set = hitting_condition_check(F, G); if (omit_set.any()) { return omit_set; } // Check 1.2: same vertices covered omit_set = coverage_condition_check(F, G); if (omit_set.any()) { return omit_set; } // Check 1.3: neither F nor G has edges too large omit_set = edge_size_check(F, G); if (omit_set.any()) { return omit_set; } // Check 2.1: satisfiability count condition omit_set = satisfiability_count_check(F, G); if (omit_set.any()) { return omit_set; } // FK step 3: Check whether F and G are small // If either hypergraph is empty, they cannot be dual omit_set = small_hypergraphs_check(F, G); if (omit_set.any()) { return omit_set; } // FK step 4: Recurse // Find the most frequently occurring vertex Hypergraph::EdgeIndex max_freq_vert = most_frequent_vertex(F, G); // Then we compute the split hypergraphs F0, F1, G0, and G1 std::pair<Hypergraph, Hypergraph> Fsplit, Gsplit; Hypergraph F0, F1, G0, G1; Fsplit = split_hypergraph_over_vertex(F, max_freq_vert); F0 = Fsplit.first; F1 = Fsplit.second; Gsplit = split_hypergraph_over_vertex(G, max_freq_vert); G0 = Gsplit.first; G1 = Gsplit.second; // We will also need the unions F0∪F1 and G0∪G1 Hypergraph Fnew = minimized_union(F0, F1); Hypergraph Gnew = minimized_union(G0, G1); // And, finally, fire up the two recursions if (F1.num_edges() > 0 and Gnew.num_edges() > 0) { BOOST_LOG_TRIVIAL(trace) << "Side 1 recursion."; Hypergraph::Edge omit_set = find_omit_set(F1, Gnew); if (omit_set.any()) { return omit_set; } } if (Fnew.num_edges() > 0 and G1.num_edges() > 0) { BOOST_LOG_TRIVIAL(trace) << "Side 2 recursion."; Hypergraph::Edge omit_set = find_omit_set(Fnew, G1); if (omit_set.any()) { omit_set.set(max_freq_vert); return omit_set; } } // If we make it this far, we did not find a nonempty // omit_set, so the pair is dual return omit_set; }
void ParBMAlgorithm::find_new_hses (const Hypergraph& H, const Hypergraph& G, const Hypergraph::Edge& c, Hypergraph::EdgeQueue& results) const { /** Find any new hitting sets of H^c with respect to G_c, queueing the results. **/ // Construct the reduced hypergraphs Hypergraph Hc = H.contraction(c, false); Hypergraph Gc = G.restriction(c); BOOST_LOG_TRIVIAL(trace) << "Starting transversal build with |Hc| = " << Hc.num_edges() << " and |Gc| = " << Gc.num_edges(); // Initialize a candidate hitting set to store intermediate results Hypergraph::Edge new_hs = Hc.verts_covered(); // Step 1: Initialize Gc if it is empty by returning the set of all vertices if (Gc.num_edges() == 0) { // If any edge of Hc is empty, this is a dead end for (const auto& e: Hc) { if (e.none()) { return; } } // Otherwise, the support of Hc is a new hitting set results.enqueue(new_hs); return; } // Step 2: Handle |Gc| = 1 if (Gc.num_edges() == 1) { assert(Hc.num_edges() != 0); // Check whether H has a singleton for every vertex it covers Hypergraph::Edge Hc_verts_to_cover = Gc[0]; for (const auto& e: Hc) { if (e.count() == 1) { Hc_verts_to_cover -= e; } } if (Hc_verts_to_cover.none()) { // If so, this is a dead end return; } else { // If not, we choose some vertex that was hit by Gc but was // not a singleton in Hc Hypergraph::EdgeIndex uncovered_vertex = Hc_verts_to_cover.find_first(); new_hs.reset(uncovered_vertex); results.enqueue(new_hs); return; } // We definitely shouldn't ever be here! throw std::runtime_error("invalid state in |Gc|=1 case."); } // Step 3: Split up subcases using a full cover // Construct I according to lemmas 7 and 8 Hypergraph::Edge I = Gc.vertices_with_degree_above_threshold(0.5); // Per lemma 7, look for an edge that does not intersect I Hypergraph::Edge missed_edge = find_missed_edge(Hc, I); // If found, form a full cover if (missed_edge.any()) { Hypergraph C = l4_full_cover(Hc, missed_edge); find_new_hses_fork(H, G, C, results); return; } // Per lemma 8, look for a transversal that covers I Hypergraph::Edge subtrans_edge = find_subset_edge(Gc, I); // If found, form a full cover if (subtrans_edge.any()) { Hypergraph C = l5_full_cover(Hc, subtrans_edge); find_new_hses_fork(H, G, C, results); return; } // If we reach this point, I itself is a new HS results.enqueue(I); return; }