I have a
typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::bidirectionalS, VertexInfo, EdgeInfo > Graph;
That I want a copy of with all edges reversed. I want a copy and not a view since I want to change the edge weights of this graph without modifying the original data structure.
boost::reverse_graph gives a view (with a different type)
You can use make_reverse_graph:
E.g. input
Graph read() {
Graph g;
using namespace boost;
dynamic_properties dp;
dp.property("node_id", get(&VertexInfo::label, g));
read_graphviz("digraph { a -> b; a -> c; b -> d; b -> e; e -> f; e -> c; }", g, dp);
return g;
}
Graph:
Reversal:
int main() {
using namespace boost;
auto g = read();
auto r = make_reverse_graph(g);
write_graphviz(std::cout, r, make_label_writer(get(&VertexInfo::label, r)));
}
Graph
Full Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/reverse_graph.hpp>
#include <libs/graph/src/read_graphviz_new.cpp>
struct VertexInfo {
std::string label;
};
struct EdgeInfo {
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, VertexInfo, EdgeInfo> Graph;
Graph read() {
Graph g;
using namespace boost;
dynamic_properties dp;
dp.property("node_id", get(&VertexInfo::label, g));
read_graphviz("digraph { a -> b; a -> c; b -> d; b -> e; e -> f; e -> c; }", g, dp);
return g;
}
int main() {
using namespace boost;
auto g = read();
auto r = make_reverse_graph(g);
write_graphviz(std::cout, r, make_label_writer(get(&VertexInfo::label, r)));
}
Found the answer:
boost::copy_graph(boost::make_reverse_graph(_graph), _reverse_graph);
Where _reverse_graph is type Graph
Related
I have to write code that can parse a file like this:
digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
}
Each vertex has label necessarily, but also it may have an unknown number of properties. Moreover, these properties can be own for each vertex. With edges everything is simple - only one label. I would like not to write my own parser, but to use read_graphviz. Tell me, please, how can this be done?
It would be perfect if the code worked with such bundled property:
struct Vertex {
std::string label;
std::map<std::string, std::string> attributes;
};
I wrote a code that parses this:
digraph G {
0[label="person" attributes="name=James_Cameron"];
1[label="film" attributes="title=Avatar_2;year=2022"];
0->1 [label="directed"];
}
But this format looks creepy.
My code:
#include <iostream>
#include <vector>
#include <map>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/adjacency_list.hpp>
struct Vertex {
std::string raw_attrs;
std::string label;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string label;
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, Vertex, Edge> Graph;
int main(int argc, char** argv) {
Graph graph;
boost::dynamic_properties dp(boost::ignore_other_properties);
dp.property("label", boost::get(&Vertex::label, graph));
dp.property("attributes", boost::get(&Vertex::raw_attrs, graph));
dp.property("label", boost::get(&Edge::label, graph));
std::ifstream f("file.txt");
boost::read_graphviz(f, graph, dp);
// transform raw_attrs to attributes
}
I promised to find the time to demonstrate the support for dynamic attributes in BGL read_graphviz.
I'll do that by implementing a roundtripping
int main() {
std::istringstream iss(R"(digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
})");
auto g = do_read(iss);
do_write(std::cout, g);
}
Note that it's a logical roundtrip. It doesn't promise to preserve formatting or node id numbering.
0. Preliminaries
Some useful definitions:
struct Vertex {
int original_id;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string relation;
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;
using V = G::vertex_descriptor;
1. Writing The Graph
The simpler part first. We use the overload that takes a writer interface. We can hack it in few lines:
void do_write(std::ostream& os, G& g) {
auto vw = boost::attributes_writer(get(&Vertex::attributes, g));
auto ew = boost::label_writer{get(&Edge::relation, g)};
write_graphviz(os, g, vw, ew);
}
We supply the vertex writer as the attributes writer, and write the one edge property (relation) as the label.
2. Reading The Graph
You can pass a generator function to the constructor of dynamic_properties. This will be invoked when a new attribute is found.
G do_read(std::istream& is) {
G g;
// ...
boost::dynamic_properties dp(newattr);
dp.property("label", get(&Edge::relation, g));
dp.property("node_id", get(&Vertex::original_id, g));
read_graphviz(is, g, dp);
return g;
}
Let's use a lambda to define the newattr generator function:
auto attrs = get(&Vertex::attributes, g);
auto newattr = [=](std::string const& name, auto&& descr, auto&&) -> Ptr {
if (typeid(V) == descr.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return attrs[v][name]; }));
} else
return {};
};
Where make_dyn is a simple helper to wrap a concrete property map (like our function property map) in a shared pointer to a dynamic property map:
using Ptr = boost::shared_ptr<boost::dynamic_property_map>;
auto make_dyn = [](auto m) -> Ptr {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
In production code you would probably make this a free function, which would be more re-usable and slightly more readable by getting rid of the decltype expression.
Full Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/function_property_map.hpp>
struct Vertex {
int original_id;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string relation;
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;
using V = G::vertex_descriptor;
void do_write(std::ostream& os, G& g) {
auto vw = boost::attributes_writer(get(&Vertex::attributes, g));
auto ew = boost::label_writer{get(&Edge::relation, g)};
write_graphviz(os, g, vw, ew);
}
G do_read(std::istream& is) {
using Ptr = boost::shared_ptr<boost::dynamic_property_map>;
auto make_dyn = [](auto m) -> Ptr {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
G g;
auto attrs = get(&Vertex::attributes, g);
auto newattr = [=](std::string const& name, auto&& descr, auto&&) -> Ptr {
if (typeid(V) == descr.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return attrs[v][name]; }));
} else
return {};
};
boost::dynamic_properties dp(newattr);
dp.property("label", get(&Edge::relation, g));
dp.property("node_id", get(&Vertex::original_id, g));
read_graphviz(is, g, dp);
return g;
}
int main() {
std::istringstream iss(R"(digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
})");
auto g = do_read(iss);
do_write(std::cout, g);
}
Prints
g++ -std=c++20 -O2 -pedantic -pthread main.cpp -lboost_graph && ./a.out
digraph G {
0[label=person, name="James Cameron"];
1[label=film, title="Avatar 2", year=2022];
0->1 [label=directed];
}
Writing a parser is not difficult. Here is the code for an application that parses the vertices. I hope you do not think it is creepy.
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
class cVertex
{
int userIndex;
std::string label;
std::map<std::string, std::string> attributes;
static std::vector<cVertex> myVertex;
public:
void read(const std::string &fname);
std::string textVertexList();
private:
void parseVertexLine(const std::string &line);
};
std::vector<cVertex> cVertex::myVertex;
void cVertex::read(const std::string &fname)
{
std::ifstream ifs(fname);
if (!ifs.is_open())
{
std::cout << "Cannot open file\n";
exit(1);
}
std::string line;
getline(ifs, line);
while (getline(ifs, line))
{
if (line.find("->") != -1)
break;
parseVertexLine(line);
}
}
std::string cVertex::textVertexList()
{
std::stringstream ss;
for (auto &v : myVertex)
{
ss << "\nvertex " << v.userIndex
<< " label is " << v.label << "\n";
for (auto &a : v.attributes)
{
ss << a.first << " | " << a.second << "\n";
}
}
return ss.str();
}
void cVertex::parseVertexLine(const std::string &line)
{
cVertex v;
v.userIndex = atoi(line.c_str());
int p = line.find("label");
int q = line.find("\"", p + 7);
v.label = line.substr(p + 7, q - p - 7);
while (1)
{
std::pair< std::string,std::string > ap;
p = line.find("=", q);
if (p == -1)
break;
ap.first = line.substr(q + 2, p - q - 2);
p = line.find("\"", p);
q = line.find("\"", p + 1);
ap.second = line.substr(p + 1, q - p - 1);
v.attributes.insert(ap);
int dbg = 0;
}
myVertex.push_back(v);
}
main()
{
cVertex theVertex;
theVertex.read("data.txt");
std::cout << theVertex.textVertexList();
return 0;
}
I'm studying BGL now and I found a tutorial for it. Everything worked fine from it until I reached function to add_named_vertex. Here is a piece of code I have, that does not work as I (and tutorial) expect:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <type_traits>
#include <iostream>
#include <sstream>
#include <string>
boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::directedS,
boost::property<boost::vertex_name_t, std::string>
>
create_empty_graph() { return {}; }
template<typename graph, typename name_type>
typename boost::graph_traits<graph>::vertex_descriptor
add_named_vertex(const name_type& vertex_name, graph& g) noexcept {
const auto vd = boost::add_vertex(g);
auto vertex_name_map = get(boost::vertex_name, g);
put(vertex_name_map, vd, vertex_name);
return vd;
}
int main()
{
auto g = create_empty_graph();
const auto va = add_named_vertex("One", g);
const auto vb = add_named_vertex("Two", g);
boost::add_edge(va,vb, g);
std::stringstream f;
boost::write_graphviz(f, g);
std::cout << f.str() << std::endl;
return 0;
}
I expect:
digraph G {
0[label=One];
1[label=Two];
0->1;
}
But here is what I got:
digraph G {
0;
1;
0->1;
}
As you can see, there are no labels in the output of this code. Could you please tell me, what am I missing? Is it expected behavior?
Tried both clang++ and gcc and for range of Boost version (1.69 - 1.71).
Yes it is expectd behavior. To print the labels, add a property writer:
auto vlw = boost::make_label_writer(boost::get(boost::vertex_name, g));
boost::write_graphviz(f, g, vlw);
See it Live on Coliru
Or, as I prefer, use write_graphviz_dp to use dynamic_properties:
boost::dynamic_properties dp;
dp.property("node_id", boost::get(boost::vertex_index, g));
dp.property("label", boost::get(boost::vertex_name, g));
boost::write_graphviz_dp(f, g, dp);
See it Live on Coliru
It may seem like more work, but it is easy and flexible with many vertex/edge properties. You can search my answers for good examples of this.
Both the above solutions print
digraph G {
0[label=One];
1[label=Two];
0->1 ;
}
BONUS
You don't need the add_named_vertex function. You can initialize the property directly with boost::add_vertex:
const auto va = add_vertex({"One"}, g);
const auto vb = add_vertex({"Two"}, g);
add_edge(va, vb, g);
I'm trying to generate a .dot which displays an horizontal graph with Boost Graph Library.
My code when creating my graph looks like this:
struct VertexP {
std::string tag;
};
struct EdgeP {
std::string symbol;
};
struct GraphP{
std::string orientation;
};
typedef boost::adjacency_list<boost::vecS,
boost::vecS, boost::directedS,
VertexP, EdgeP, GraphP> Graph;
GraphP property;
property.orientation = "LR";
Graph graph(property);
// Then fill the graph
The code I'm using to generate the .dot file is something like this:
Graph g = creator.AutomatonToGraph(&automaton);
ofstream dot_file("automaton.dot");
dynamic_properties dp;
dp.property("node_id", get(&VertexP::tag, g));
dp.property("label", get(&VertexP::tag, g));
dp.property("label", get(&EdgeP::symbol, g));
write_graphviz_dp(dot_file, g, dp);
This writes the .dot file perfectly with nodes and edges labels, but my problem is that I want to add the rankdir=LR graph property to the output file. I tried with:
Graph g = creator.AutomatonToGraph(&automaton);
ofstream dot_file("automaton.dot");
dynamic_properties dp;
dp.property("node_id", get(&VertexP::tag, g));
dp.property("label", get(&VertexP::tag, g));
dp.property("label", get(&EdgeP::symbol, g));
dp.property("rankdir", get(&GraphP::orientation, g));
write_graphviz_dp(dot_file, g, dp);
But I get a big chunk of errors starting by this:
/src/lab2.cc:48:55: required from here
/usr/include/boost/graph/detail/adjacency_list.hpp:2585:29: error: forming reference to void
typedef value_type& reference;
^~~~~~~~~
I'm really new using BGL, what I'm doing wrong?
Reading into the implementation of dynamic_graph_properties_writer, I figured out you should probably just do
dp.property("rankdir", boost::make_constant_property<Graph*>(std::string("LR")));
For dynamic retrieval you can use a function property map: (map set/get requests into C++ class/structure changes):
#include <boost/property_map/function_property_map.hpp>
dp.property("rankdir", boost::make_function_property_map<Graph*>([](Graph const* g) { return g->m_property->orientation; }));
Live Demo
See it Live On Wandbox
#include <boost/graph/adj_list_serialize.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <fstream>
using namespace boost;
struct VertexP { std::string tag; };
struct EdgeP { std::string symbol; };
struct GraphP { std::string orientation; };
typedef adjacency_list<vecS, vecS, directedS, VertexP, EdgeP, GraphP> Graph;
int main() {
Graph g(GraphP{"LR"});
// Then fill the graph
add_edge(
add_vertex(VertexP{ "tag1" }, g),
add_vertex(VertexP{ "tag2" }, g),
EdgeP{ "symbol" }, g
);
{
std::ofstream dot_file("automaton.dot");
dynamic_properties dp;
dp.property("node_id", get(&VertexP::tag, g));
dp.property("label", get(&VertexP::tag, g));
dp.property("label", get(&EdgeP::symbol, g));
dp.property("rankdir", boost::make_constant_property<Graph*>(std::string("LR")));
dp.property("dummy", boost::make_function_property_map<Graph*>([](Graph const* g) { return g->m_property->orientation; }));
write_graphviz_dp(dot_file, g, dp);
}
}
Which writes
digraph G {
dummy=LR;
rankdir=LR;
tag2 [label=tag2];
tag1 [label=tag1];
tag1->tag2 [label=symbol];
}
The following code has errors:
cannot bind ‘std::basic_ostream’ lvalue to ‘std::basic_ostream&&
#include <boost/graph/graphviz.hpp>
void foo(int,char*[])
{
using namespace boost;
typedef boost::adjacency_list<
boost::setS, // outedge list
boost::setS, // vertex list
boost::directedS, // undirected
boost::no_property, // vertex prop
boost::no_property, // edge prop
boost::no_property, // graph prop
boost::setS // edgelistc
> Graph;
Graph g;
std::ostringstream dotname;
dotname << "a.dot";
std::ofstream dot(dotname.str());
write_graphviz(dot, g);
}
It works when
boost::vecS, // vertex list
Is it expected?
Change the vertex container selector to setS changes the vertex descriptor into a type that is not streamable.
You should, as with many many other algorithms in BGL, pass a separate vertex index:
IN: VertexAndEdgeListGraph& g
A directed or undirected graph. The graph's type must be a model of
VertexAndEdgeListGraph. In most cases, the graph must have an internal
vertex_index property map.
External Vertex Id mapping
Live On Coliru
#include <boost/graph/graphviz.hpp>
#include <boost/graph/random.hpp>
#include <random>
int main(int,char*[])
{
using namespace boost;
typedef boost::adjacency_list<setS, setS, directedS> Graph;
Graph g;
std::mt19937 prng{std::random_device{}()};
generate_random_graph(g, 3, 5, prng);
std::map<Graph::vertex_descriptor, size_t> ids;
for (auto u : make_iterator_range(vertices(g)))
ids[u] = ids.size();
default_writer w;
write_graphviz(std::cout, g, w, w, w, make_assoc_property_map(ids));
}
Prints e.g.
digraph G {
1;
2;
3;
1->2 ;
2->1 ;
2->3 ;
3->1 ;
3->2 ;
}
Internal property map:
You can put the property internally without much changing:
Live On Coliru
#include <boost/graph/graphviz.hpp>
#include <boost/graph/random.hpp>
#include <random>
int main(int,char*[])
{
using namespace boost;
typedef boost::adjacency_list<setS, setS, directedS, property<vertex_index_t, size_t> > Graph;
Graph g;
std::mt19937 prng{std::random_device{}()};
generate_random_graph(g, 3, 5, prng);
auto ids = get(vertex_index, g);
size_t num = 0;
for (auto u : make_iterator_range(vertices(g)))
put(ids, u, num++);
write_graphviz(std::cout, g);
}
I'm struggling trying to manually colouring graph's vertices using boost. I wrote the code below but I can't figure it out why the generated file does not have any colour.
int main(int,char*[]) {
typedef property<edge_name_t, string> EdgeProperties;
typedef property<vertex_name_t, string, property<vertex_color_t, default_color_type>> VertexProperties;
typedef adjacency_list<vecS, vecS, directedS, VertexProperties, EdgeProperties> Graph;
typedef graph_traits<Graph>::vertex_descriptor Vertex;
typedef graph_traits<Graph>::edge_descriptor Edge;
Graph g;
property_map<Graph, vertex_name_t>::type vertex_label = get(vertex_name, g);
property_map<Graph, vertex_color_t>::type color_map = get(vertex_color, g);
property_map<Graph, edge_name_t>::type edge_label = get(edge_name, g);
Vertex v1 = add_vertex(g);
vertex_label[v1] = "v1";
put(color_map, v1, boost::red_color);
std::ofstream outf("example.gv");
write_graphviz(outf, g,
make_label_writer(vertex_label),
make_label_writer(edge_label)
);
return 0;
}
Vertex colourings are really an algorithmic detail. There is no "automatic" provision to translate it to graphviz, as far as I know.
You can add custom attributes though.
I'd usually change it around to use dynamic properties:
std::ofstream outf("example.gv");
dynamic_properties dp;
dp.property("node_id", get(vertex_index, g));
dp.property("label", vertex_label);
dp.property("label", edge_label);
write_graphviz_dp(outf, g, dp);
Now it's as simple as adding a new vertex attribute to the dynamic set:
dp.property("color", make_transform_value_property_map(&color_to_string, color_map));
The only complication is that you need to supply the conversion from default_color_type to text. This is another symptom of the fact that the color maps aren't usually used for representational purposes. Here's a fully working sample:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <iostream>
using namespace boost;
inline char const* color_to_string(default_color_type c) {
switch(c) {
case default_color_type::red_color: return "red";
case default_color_type::green_color: return "green";
case default_color_type::gray_color: return "gray";
case default_color_type::white_color: return "white";
case default_color_type::black_color: return "black";
}
return ""; // not known
}
int main() {
typedef property<edge_name_t, std::string> EdgeProperties;
typedef property<vertex_name_t, std::string, property<vertex_color_t, default_color_type>> VertexProperties;
typedef adjacency_list<vecS, vecS, directedS, VertexProperties, EdgeProperties> Graph;
typedef graph_traits<Graph>::vertex_descriptor Vertex;
Graph g;
property_map<Graph, vertex_name_t>::type vertex_label = get(vertex_name, g);
property_map<Graph, vertex_color_t>::type color_map = get(vertex_color, g);
property_map<Graph, edge_name_t>::type edge_label = get(edge_name, g);
Vertex v1 = add_vertex(g);
vertex_label[v1] = "v1";
put(color_map, v1, boost::red_color);
dynamic_properties dp;
dp.property("node_id", get(vertex_index, g));
dp.property("label", vertex_label);
dp.property("label", edge_label);
dp.property("color", make_transform_value_property_map(&color_to_string, color_map));
write_graphviz_dp(std::cout, g, dp);
}
Prints:
digraph G {
0 [color=red, label=v1];
}