【问题标题】:Boost.Graph and Graphviz nested subgraphsBoost.Graph 和 Graphviz 嵌套子图
【发布时间】:2019-02-08 19:38:40
【问题描述】:

我期待代码

#include <boost/graph/graphviz.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/subgraph.hpp>
#include <iostream>

using namespace boost;

using attrs_t = std::map<std::string, std::string>;

using graph_t = adjacency_list<
    vecS, vecS, directedS,
    property<vertex_attribute_t, attrs_t>,
    property<edge_index_t, int, property<edge_attribute_t, attrs_t>>,
    property<graph_name_t, std::string,
        property<graph_graph_attribute_t, attrs_t,
            property<graph_vertex_attribute_t, attrs_t,
                property<graph_edge_attribute_t, attrs_t>>>>>;

int main()
{
    char names[] = {"AB"};
    enum {A, B, N};

    subgraph<graph_t> main(N);
    subgraph<graph_t>& sub1 = main.create_subgraph();
    subgraph<graph_t>& sub2 = sub1.create_subgraph();

    add_vertex(A, sub1);
    add_vertex(B, sub2);
    add_edge(A, B, main);

    get_property(main, graph_name) = "G0";
    get_property(sub1, graph_name) = "clusterG1";
    get_property(sub2, graph_name) = "clusterG2";

    write_graphviz(std::cout, main, make_iterator_vertex_map(names));
}

生成左边的图表,而我得到了右边的:

输出是:

digraph G0 {
subgraph clusterG1 {
subgraph clusterG2 {
//B;
}
//A;
}
A -> B;
}

注释的节点语句是层次信息丢失的地方(我的输出中没有这些行)。我怎样才能避免这种情况?


如果我将两个顶点都添加到同一个子图中:

add_vertex(A, sub1);
add_vertex(B, sub1);
add_edge(A, B, main);

连接A -&gt; B 出现在clusterG1 的范围内,根据我的理解,这就是所提到的顶点也将被隐式声明的地方。

我正在使用 Boost 1.68.0

【问题讨论】:

    标签: c++ boost graphviz boost-graph


    【解决方案1】:

    好地方。正如您自己的回答所解释的那样,linked answer 实际上有 UB。传递给write_graphviz 函数的映射实际上不应该具有用于​​graphviz 输出的node_id。相反,该映射被假定为 vertex_index_t 属性映射。

    这是我可能从boost::print_graph (graph_utility.hpp) 得出的一个假设,确实这样的属性映射。

    为了使其安全工作,我将修改示例以使用 write_graphviz_dp - 使用动态属性:

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

    我选择使用转换函数来获取任何顶点描述符的名称,也不想再假设顶点的数量,我编写了更通用的函数来生成名称,例如“A”,..., "Z","AA",...,"ZZ" 等:

    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

    保留子图信息

    上述重载不支持子图。因此,让我们修复 vertex_attribute 映射以具有预期的顶点标签:

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

    现在它确实打印了:

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

    呈现为

    完整列表

    为后代保留

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

    【讨论】:

    • 是的,胜利。找到了让标签保留子图信息的方法。在 ASan/UBSan 下确认清洁(请参阅 imageLive Demo
    • 我喜欢 Boost Graph,但特别是在“外部支持”文档中严重缺乏。但我想这就是程序员的生活。为让我解决这些问题而欢呼。
    【解决方案2】:

    问题是由提供make_iterator_vertex_map(names) 引起的,它从names'A''B')返回chars。这与预期的(和默认的)vertex_index 属性(01)不兼容。修复后,我得到:

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

    vertex_marker,在我的例子中是std::vector&lt;bool&gt;2 元素,而不是67,并且预计更多元素会被顶点索引索引。我遇到了未定义的行为,
    if ( vertex_marker[pos] ) 从未实现过。

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

    vertex_id 参数类型的 static_assert/SFINAE 与 Graphvertex_id 类型(可以立即在 boost::write_graphviz 中完成)将非常受欢迎。附带说明一下,硬编码的int pos = ... 看起来不太专业……

    如果我们想保留自定义名称,我们必须改为通过label 属性提供它们。

    【讨论】:

    • 是的,这是故事的一部分。
    猜你喜欢
    • 1970-01-01
    • 2021-11-22
    • 2021-03-20
    • 2023-03-14
    • 1970-01-01
    • 2012-11-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-25
    相关资源
    最近更新 更多