Boost.Graph and Graphviz nested subgraphs

后端 未结 2 1713
北海茫月
北海茫月 2021-01-27 00:52

I expected the code

#include 
#include 
#include 
#include          


        
相关标签:
2条回答
  • 2021-01-27 01:21

    The trouble was caused by supplying make_iterator_vertex_map(names), which returns chars from names ('A', 'B'). This is incompatible with the expected (and default) vertex_index properties (0, 1). After the fix, I get:

    digraph G0 {
    subgraph clusterG1 {
    subgraph clusterG2 {
    1;
    }
    0;
    }
    0 -> 1;
    }
    

    vertex_marker, which is std::vector<bool> of 2 elements in my case, not 67 and more was expected to be indexed by vertex indices. I was experiencing undefined behavior,
    and if ( vertex_marker[pos] ) was never fulfilled.

    The responsible portion of boost::detail::write_graphviz_subgraph:

    // Print out vertices and edges not in the subgraphs.
    
    typename graph_traits<Graph>::vertex_iterator i, end;
    typename graph_traits<Graph>::edge_iterator ei, edge_end;
    
    for(boost::tie(i,end) = vertices(g); i != end; ++i) {
      Vertex v = g.local_to_global(*i);
      int pos = get(vertex_id, v); // 66, 65, should be 1, 0
      if ( vertex_marker[pos] ) {  // out-of-bounds access
        vertex_marker[pos] = false;
        out << escape_dot_string(pos);
        make_vertex_attributes_writer(g.root())(out, v);
        out << ";" << std::endl;
      }
    }
    

    A static_assert/SFINAE of the vertex_id parameter type against Graph's vertex_id type (which can be done immediately in boost::write_graphviz) would be greatly welcome. On a side note, the hardcoded int pos = ... does not look too professional...

    And if we want to keep the custom names, we have to provide them via label attributes instead.

    0 讨论(0)
  • 2021-01-27 01:27

    Good spot. The linked answer actually has UB, as your own answer explains. The map passed in to the write_graphviz function isn't actually supposed to have the node_id for the graphviz output. Instead, that map was assumed to be a vertex_index_t propertymap.

    That was an assumption I probably held from boost::print_graph (graph_utility.hpp) that does that such property-map.

    To make it work safely I'd modify the example to employ write_graphviz_dp - using dynamic properties:

    int main() {
        boost::dynamic_properties dp;
        dp.property("node_id", boost::make_transform_value_property_map<std::string>(&name_for_index, boost::identity_property_map{}));
        write_graphviz_dp(std::cout, create_data<subgraph<Graph> >(), dp);
    }
    

    I opted to use a transform function to get a name for any vertex descriptor, not wanting to assume anything about the number of vertices anymore either, I wrote the more general function to generate names like "A",...,"Z","AA",...,"ZZ" etc.:

    static std::string name_for_index(intmax_t index) {
        std::string name;
    
        do {
            name += 'A' + (index%26);
            index /= 26;
        } while (index);
    
        return name;
    }
    

    Live On Coliru

    Retaining Subgraph Information

    The above overload doesn't have the subgraph support. So, instead let's fix the vertex_attribute map to have the expected vertex labels:

    int main() {
        auto g = create_data<subgraph<Graph> >();
    
        for (auto vd : make_iterator_range(vertices(g))) {
            put(get(vertex_attribute, g), vd, 
                    GraphvizAttributes{
                        {"label", name_for_index(vd)}
                    });
        }
    
        write_graphviz(std::cout, g);
    }
    

    Now it does print:

    Live On Coliru

    digraph G0 {
    subgraph clusterG1 {
    graph [
    label=G1];
    node [
    color=red, shape=Mrecord];
    0[label="Vertex A"];
    1[label="Vertex B"];
    0 -> 1;
    }
    subgraph clusterG2 {
    graph [
    fillcolor=lightgray, label=G2, style=filled];
    node [
    shape=circle];
    4[label="Vertex E"];
    2[label="Vertex C"];
    5[label="Vertex F"];
    4 -> 5;
    2 -> 5;
    }
    3[label="Vertex D"];
    1 -> 2;
    1 -> 3;
    4 -> 1;
    5 -> 3;
    }
    

    Which renders as

    Full Listing

    Preserving for posterity

    #include <boost/graph/graphviz.hpp>
    #include <boost/graph/adjacency_list.hpp>
    #include <boost/graph/subgraph.hpp>
    #include <iostream>
    
    using namespace boost;
    
    template <typename SubGraph> SubGraph create_data()
    {
        enum { A,B,C,D,E,F,N }; // main edges
        SubGraph main(N);
    
        SubGraph& sub1 = main.create_subgraph();
        SubGraph& sub2 = main.create_subgraph();
    
        auto A1 = add_vertex(A, sub1);
        auto B1 = add_vertex(B, sub1);
    
        auto E2 = add_vertex(E, sub2);
        auto C2 = add_vertex(C, sub2);
        auto F2 = add_vertex(F, sub2);
    
        add_edge(A1, B1, sub1);
        add_edge(E2, F2, sub2);
        add_edge(C2, F2, sub2);
    
        add_edge(E, B, main);
        add_edge(B, C, main);
        add_edge(B, D, main);
        add_edge(F, D, main);
    
        // setting some graph viz attributes
        get_property(main, graph_name) = "G0";
        get_property(sub1, graph_name) = "clusterG1";
        get_property(sub2, graph_name) = "clusterG2";
    
        get_property(sub1, graph_graph_attribute)["label"]              = "G1";
        /*extra*/get_property(sub1, graph_vertex_attribute)["shape"]    = "Mrecord";
    
        get_property(sub2, graph_graph_attribute)["label"]              = "G2";
        /*extra*/get_property(sub1, graph_vertex_attribute)["color"]    = "red";
        /*extra*/get_property(sub2, graph_graph_attribute)["fillcolor"] = "lightgray";
        /*extra*/get_property(sub2, graph_graph_attribute)["style"]     = "filled";
        /*extra*/get_property(sub2, graph_vertex_attribute)["shape"]    = "circle";
    
        return main;
    }
    
    using GraphvizAttributes = 
        std::map<std::string, std::string>;
    
    using Graph =
        adjacency_list<vecS, vecS, directedS, 
            property<vertex_attribute_t, GraphvizAttributes>,
            property<edge_index_t, int, property<edge_attribute_t, GraphvizAttributes> >,
            property<graph_name_t, std::string,
            property<graph_graph_attribute_t,  GraphvizAttributes,
            property<graph_vertex_attribute_t, GraphvizAttributes,
            property<graph_edge_attribute_t,   GraphvizAttributes>
            > > >
        >;
    
    static std::string name_for_index(intmax_t index) {
        std::string name = "Vertex ";
    
        do {
            name += 'A' + (index%26);
            index /= 26;
        } while (index);
    
        return name;
    }
    
    int main() {
        auto g = create_data<subgraph<Graph> >();
    
        for (auto vd : make_iterator_range(vertices(g))) {
            put(get(vertex_attribute, g), vd, 
                    GraphvizAttributes{
                        {"label", name_for_index(vd)}
                    });
        }
    
        write_graphviz(std::cout, g);
    }
    
    0 讨论(0)
提交回复
热议问题