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;
    }