Search code examples
c++boostboost-graphadjacency-listconnected-components

Can Boost Graph Find Connected Components Based on Weight?


I'm successfully using boost graph's component finder to assign color, i.e. the component's index to every vertex in my graph like so:

#include <boost/graph/connected_components.hpp>
#include <boost/graph/adjacency_list.hpp>

boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> g;

std::vector<int> compon_map(boost::num_vertices(g));

int number_of_components = boost::connected_components(g, &compon_map[0]);

This will then yield a different number_of_components after each iteration in my simulation (not shown) because I do

boost::clear_vertex(v, g);

in between to erase some edges based on some condition.

The thing is that in my simulation I want to write out some property (say weight) of all edges, and the length of the edge iterator needs to stay constant (dataset restrictions).

My question is therefore: Is there a way to pass some edge property like a

int L = boost::num_edges(g);

std::vector<bool> is_still_existent(L); // or
std::vector<double> edge_weights(L);

to the boost::connected_components (that then counts edges only based on that property) or is there another way to trick the edge iterator to stay at the initial length even after having removed edges?

Thanks in advance for any hints :)


Solution

  • Yes. You can use a filtered graph adaptor with an edge filter. I have several answers up on SO showing how to use it, but will see whether I can usefully create an example based on on your snippet.

    So I made a self-contained sample¹: Live On Coliru,

    #include <boost/graph/adjacency_list.hpp>
    #include <boost/graph/connected_components.hpp>
    #include <boost/property_map/transform_value_property_map.hpp>
    #include <boost/graph/random.hpp>
    #include <boost/graph/graphviz.hpp>
    #include <random>
    
    using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;
    using V = G::vertex_descriptor;
    using E = G::edge_descriptor;
    using W = double;
    
    int main(int argc, char** argv) {
        G g;
        auto seed = 9353; // fixed seed for demo
        if (argc > 1) {
            seed = atol(argv[1]);
            std::cerr << "Using PRNG seed: " << seed << "\n";
        }
    
        std::mt19937 engine(seed);
        auto weight_gen = bind(std::uniform_real_distribution<W>(0, 1), engine);
        boost::generate_random_graph(g, 10, 6, engine);
    
        std::map<E, W> weights;
    
        for (auto e : boost::make_iterator_range(edges(g)))
            weights[e] = weight_gen();
        
        std::vector<int> components(boost::num_vertices(g));
        auto cmap = boost::make_iterator_vertex_map(components.data());
    
    
        int n = boost::connected_components(g, cmap);
    
        std::cerr << n << " components\n";
    
        boost::dynamic_properties dp;
        dp.property("node_id", get(boost::vertex_index, g));
        dp.property("style", boost::make_constant_property<V>(std::string("filled")));
        dp.property("color",
                    boost::make_transform_value_property_map(
                        [](int componentid) {
                            static std::array cc{"red",    "green", "yellow",
                                                 "blue",   "brown", "black",
                                                 "orange", "purple"};
                            return cc[componentid % cc.size()];
                        },
                        cmap));
        dp.property("label", boost::make_assoc_property_map(weights));
    
        boost::write_graphviz_dp(std::cout, g, dp);
    }
    

    which generates a pseudo-random graph:

    enter image description here

    Let's add some filtering to it:

    Live On Coliru

    #include <boost/graph/adjacency_list.hpp>
    #include <boost/graph/connected_components.hpp>
    #include <boost/property_map/transform_value_property_map.hpp>
    #include <boost/property_map/function_property_map.hpp>
    #include <boost/graph/random.hpp>
    #include <boost/graph/graphviz.hpp>
    #include <random>
    
    using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;
    using V = G::vertex_descriptor;
    using E = G::edge_descriptor;
    using W = double;
    
    struct NonRemoved {
        std::set<E> const* _ref;
        bool operator()(E e) const { return not _ref->contains(e); }
    };
    
    int main(int argc, char** argv)
    {
        G g;
        auto seed = 9353; // fixed seed for demo
        if (argc > 1) {
            seed = atol(argv[1]);
            std::cerr << "Using PRNG seed: " << seed << "\n";
        }
    
        std::mt19937 engine(seed);
        auto weight_gen = bind(std::uniform_real_distribution<W>(0, 1), engine);
        boost::generate_random_graph(g, 10, 6, engine);
    
        std::map<E, W> weights;
    
        for (auto e : boost::make_iterator_range(edges(g)))
            weights[e] = weight_gen();
        
        std::vector<int> components(boost::num_vertices(g));
        auto cmap = boost::make_iterator_vertex_map(components.data());
    
        auto random_edges = [&g] {
            auto [f,l] = edges(g);
            std::deque<E> re(f,l);
            std::random_shuffle(begin(re), end(re));
            return re;
        }();
    
        std::set<E> removed;
        NonRemoved predicate{&removed};
    
        boost::filtered_graph<G, NonRemoved, boost::keep_all> f(g, predicate, {});
        do {
            int n = boost::connected_components(f, cmap);
            std::cerr << n << " components\n";
    
            boost::dynamic_properties dp;
            dp.property("node_id", get(boost::vertex_index, f));
            dp.property("style", boost::make_constant_property<V>(std::string("filled")));
            dp.property("color",
                        boost::make_transform_value_property_map(
                            [](int componentid) {
                                static std::array cc{"red",    "green", "yellow",
                                                     "blue",   "brown", "black",
                                                     "orange", "purple"};
                                return cc[componentid % cc.size()];
                            },
                            cmap));
            dp.property("color",
                        boost::make_function_property_map<E>([&removed](E e) {
                            return removed.contains(e) ? "red" : "blue";
                        }));
            dp.property("label",
                boost::make_function_property_map<E>([&removed, &weights](E e) {
                    if (removed.contains(e))
                        return std::string("REMOVED");
                    return std::to_string(weights.at(e));
                }));
    
            std::ofstream ofs("graph_" + std::to_string(random_edges.size()) + ".dot");
            boost::write_graphviz_dp(ofs, f, dp);
    
            removed.insert(random_edges.front());
            random_edges.pop_front();
        } while (not random_edges.empty());
    }
    

    Now writes a series of graph_XXX.dot graphs that display as:

    enter image description here


    ¹ (changing the vertex container selector to vecS for simplicity)