问题
I expected the code
#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));
}
to generate the graph on the left, whereas I have got the right one:
The output is:
digraph G0 {
subgraph clusterG1 {
subgraph clusterG2 {
//B;
}
//A;
}
A -> B;
}
The commented node statements are where the hierarchy information is lost (these lines I do not have in my output). How can I avoid that?
If I add both vertices into the same subgraph:
add_vertex(A, sub1);
add_vertex(B, sub1);
add_edge(A, B, main);
the connection A -> B
appears in the scope of clusterG1
and from how I understand it, that is where the mentioned vertices will also be implicitly declared.
I am using Boost 1.68.0
回答1:
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
回答2:
The trouble was caused by supplying make_iterator_vertex_map(names)
, which returns char
s 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.
来源:https://stackoverflow.com/questions/54599136/boost-graph-and-graphviz-nested-subgraphs