iterate over bundled properties in boost graph in a direct way - c++

I'm using the Boost graph library exploiting the following definitions
template <typename Vertex>
using graph = boost::adjacency_list<boost::listS,
boost::vecS,
boost::bidirectionalS,
Vertex>
template <typename Vertex>
using vertex = typename graph<Vertex>::vertex_descriptor;
where the template parameter Vertex is employed to use the BGL with bundled properties.
I'm not that confident with boost at the moment and I'd like to understand if is there a way to get the begin() and end() iterators of the "bundled property list".
In other words, let's assume I have this struct
struct City
{
string name;
int population;
vector<int> zipcodes;
};
and I want to use it as template parameter for my graph. I know I can iterate over vertexes like this
(assuming g is my adjacency list)
for(auto vd : boost::make_iterator_range(vertices(g))
{
auto v = g[*vd];
// do something with v
}
yet I'd like to do this
for(auto & v : some_iterator)
// v is a city ...
is there any way to do this ?

I've adapted my code from your now-deleted question to the more specific example. By now you already realize you are NOT in fact iterating vertices, but their bundles.
I suggest you use a transforming/indirected iterator adaptor. The easiest way I can think of is to use Boost Range Adaptors, specifically using transformed:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/range/adaptors.hpp>
using boost::adaptors::transformed;
template <typename Vertex> struct Wrapper {
using graph = boost::adjacency_list<boost::listS, boost::vecS,
boost::bidirectionalS, Vertex>;
using vertex = typename graph::vertex_descriptor;
auto bundles() {
auto accessor = [map = get(boost::vertex_bundle, _g)](vertex v) -> auto& {
return map[v];
};
return vertices(_g) | transformed(accessor);
}
auto bundles() const {
auto accessor = [map = get(boost::vertex_bundle, _g)](vertex v) -> auto& {
return map[v];
};
return vertices(_g) | transformed(accessor);
}
private:
graph _g {5};
};
struct City {
std::string name;
int population;
std::vector<int> zipcodes;
};
static int id_gen = 0;
struct Sample2 { int id = ++id_gen; };
#include <iostream>
#include <iomanip>
int main() {
Wrapper<City> demo;
for (City& c : demo.bundles()) {
c.name = "name " + std::to_string(rand() % 100);
c.population = rand() % 128 * 190'000;
c.zipcodes = {1234, 2345};
}
auto const& const_ref = demo;
for (City const& c : const_ref.bundles()) {
std::cout << std::quoted(c.name) << ", population " << c.population << "\n";
}
}
Prints
"name 83", population 13300000
"name 77", population 21850000
"name 93", population 24130000
"name 86", population 20520000
"name 49", population 14630000

Related

Pass a template to an async function

I'm trying to run an async function passing to it a function f to execute and a template function f0 as attribute.
This is the function that I create through a template
DivideVerticesInThreads<0> f0(g, {}, {});
The template is
template <int NSegments, int Segment> struct SegmentVertices {
std::hash<Graph::vertex_descriptor> _h;
bool operator()(Graph::vertex_descriptor vd) const { return (_h(vd) % NSegments) == Segment; }
};
template <int N>
using DivideVerticesInThreads = boost::filtered_graph<Graph, boost::keep_all, SegmentVertices<4, N>>;
Then I call the async function in this way
auto handle = async(std::launch::async,f, f0);
And I pass to it the function f:
auto f = [&](G const& f0) {
for(vp = vertices(f0); vp.first != vp.second; ++vp.first){
//...
}
};
The complete piece of code is:
template<typename G>
void parallel_count_adj_luby(G g){
property_map<Graph,vertex_degree_t>::type deg = get(vertex_degree, g);
auto f = [&](G const& g1) {
for(vp = vertices(g1); vp.first != vp.second; ++vp.first){
// ...
}
};
DivideVerticesInThreads<0> f0(g, {}, {});
auto handle = async(std::launch::async,f, f0);
}
The problem is that the async function gives me this error
error: no matching function for call to 'async(std::launch, parallel_count_adj_luby(G) [with G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, boost::property<boost::vertex_color_t, int, boost::property<boost::vertex_degree_t, int> > >]::<lambda(const boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, boost::property<boost::vertex_color_t, int, boost::property<boost::vertex_degree_t, int> > >&)>&, DivideVerticesInThreads<0>&)'
auto handle = async(std::launch::async,f, f0);
I don't know well c++ and future, so the error may be something dummy. What I did wrong?
I agree somewhat with the commenters, that the problem exposition is needlessly unclear.¹
However, I think I know what's happening is due to a choice I made when presenting this sample code to you in an earlier answer.
It appears I focused on efficiency and it landed you with c++ challenges you didn't know how to handle.
I'm just going to ignore the confusion and remove the static type parameters with runtime parameters. That way your filtered graph segments can all have the same static type
Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/random.hpp>
#include <iostream>
#include <random>
static std::mt19937 s_prng(std::random_device{}());
using G = boost::adjacency_list<>;
using V = G::vertex_descriptor;
struct SegmentVertices {
unsigned nsegments, segment;
bool operator()(V vd) const {
return (std::hash<V>{}(vd) % nsegments) == segment;
}
};
using F = boost::filtered_graph<G, boost::keep_all, SegmentVertices>;
G make_graph() {
G g;
generate_random_graph(g, 32 * 1024 - (s_prng() % 37), 64 * 1024, s_prng);
return g;
}
void the_function(G const& g)
{
std::cout << "Full graph " << size(boost::make_iterator_range(vertices(g)))
<< " vertices\n";
}
void the_function(F const& f)
{
auto& pred = f.m_vertex_pred;
std::cout << pred.segment << "/" << pred.nsegments << " "
<< size(boost::make_iterator_range(vertices(f))) << " vertices\n";
}
int main()
{
G g = make_graph();
the_function(g);
unsigned NSegments = s_prng() % 10 + 2;
for (unsigned seg = 0; seg < NSegments; ++seg) {
the_function(F(g, {}, {seg, NSegments}));
}
}
Prints e.g.
Full graph 32736 vertices
0/3 10912 vertices
1/3 10912 vertices
2/3 10912 vertices
Or
Full graph 32741 vertices
0/7 4678 vertices
1/7 4678 vertices
2/7 4677 vertices
3/7 4677 vertices
4/7 4677 vertices
5/7 4677 vertices
6/7 4677 vertices
As you can see, using runtime filter parameters trades performance for runtime flexibility and type convenience.
¹ E.g. even the first claim "This is the function that I create through a template" makes no sense, because what follows is not a function and no template is being used to create it either.

How to efficiently use boost graph

I have the following piece of code:
struct Vertex {
std::string name;
};
struct Edge {
std::string name;
};
class MyGraph {
public:
MyGraph() = default;
using Graph = adjacency_list<vecS, vecS, bidirectionalS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
using VertexDesc = graph_traits<Graph>::vertex_descriptor;
using EdgeDesc = graph_traits<Graph>::edge_descriptor;
void addNode(std::shared_ptr<Vertex> node){
const auto name = node->name;
if (vertexMap_.find(name) == vertexMap_.end()) {
const auto vertex = add_vertex(node, graph_);
vertexLabelArray_.emplace_back(name);
vertexMap_[name] = vertex;
}
}
void addEdge(std::shared_ptr<Vertex> src, std::shared_ptr<Vertex> dst, std::shared_ptr<Edge> weight = nullptr) {
const auto srcName = src->name;
const auto dstName = dst->name;
const auto vertexPair = std::make_pair(srcName, dstName);
if (edgeSet_.find(vertexPair) == edgeSet_.end()) {
addNode(src);
addNode(dst);
const auto edge = add_edge(vertexMap_[srcName], vertexMap_[dstName], weight, graph_).first;
edgeSet_.insert(vertexPair);
edgeLabelMap_[edge] = weight ? weight->name : "";
}
}
void print(std::ostream& out)
{
write_graphviz(out, graph_,
make_label_writer(vertexLabelArray_.data()),
make_label_writer(make_assoc_property_map(edgeLabelMap_)));
}
private:
std::vector<std::string> vertexLabelArray_;
std::map<EdgeDesc, std::string> edgeLabelMap_;
std::map<std::string, VertexDesc> vertexMap_;
std::set<std::pair<std::string, std::string>> edgeSet_;
Graph graph_;
};
struct Node : Vertex {};
struct Arc : Edge {};
int main() {
MyGraph g;
const auto n1 = std::make_shared<Node>(Node{{"n1"}});
const auto n2 = std::make_shared<Node>(Node{{"n2"}});
const auto e1 = std::make_shared<Arc>(Arc{{"e1"}});
g.addEdge(n1, n2, e1);
g.print(std::cout);
}
I would like to reduce some of the potential redundancies:
1 - how can i use setS instead of vecS to avoid checking is a vertex/edge exists. when i do so, the write_graphiz function complains that it fails with lots of errors beginning with
error: cannot form a reference to 'void' typedef const value_type& const_reference;
2 - i am using a shared_ptr for allowing for custom object to be attached to vertices and edges. is there an easy way for looking up a vertex index based on its attached object?
3 - Is it possible to remove most of the external data structures and use boost property instead somehow?
the whole thing is uploaded here: https://godbolt.org/z/qbEnEoYj3
any help is appreciated.
1 - how can i use setS instead of vecS to avoid checking is a
Q.: vertex/edge exists. when i do so, the write_graphiz function complains
that it fails with lots of errors beginning with
Sure. In fact the edge selector is no problem to begin with: https://godbolt.org/z/b1xbYMM4s. Making the vertex containter setS won't do what you mean, because every added vertex is unique by definition.
But in the interest of showing the technical issue at hand: there is no implicit vertex index (ordinal numbering [0..numvertices()) for that storage strategy. Some algorithms require one. Also, you assume one with VertexLabelArray_ which no longer makes sense, so let's change into VertexLabelMap_ just like for edge labels:
Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
struct Vertex {
std::string name;
};
struct Edge {
std::string name;
};
class MyGraph {
public:
MyGraph() = default;
using Graph = boost::adjacency_list< //
boost::setS, boost::listS, boost::bidirectionalS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
using VertexDesc = boost::graph_traits<Graph>::vertex_descriptor;
using EdgeDesc = boost::graph_traits<Graph>::edge_descriptor;
void addNode(std::shared_ptr<Vertex> node)
{
const auto name = node->name;
if (vertexMap_.find(name) == vertexMap_.end()) {
const auto vertex = add_vertex(node, graph_);
vertexLabelMap_.emplace(vertex, name);
vertexMap_[name] = vertex;
}
}
void addEdge(std::shared_ptr<Vertex> src, std::shared_ptr<Vertex> dst,
std::shared_ptr<Edge> weight = nullptr)
{
const auto srcName = src->name;
const auto dstName = dst->name;
const auto vertexPair = std::make_pair(srcName, dstName);
if (edgeSet_.find(vertexPair) == edgeSet_.end()) {
addNode(src);
addNode(dst);
const auto edge = add_edge(vertexMap_[srcName], vertexMap_[dstName],
weight, graph_)
.first;
edgeSet_.insert(vertexPair);
edgeLabelMap_[edge] = weight ? weight->name : "";
}
}
void print(std::ostream& out)
{
std::map<VertexDesc, int> idmap;
for (auto v : boost::make_iterator_range(vertices(graph_))) {
idmap.emplace(v, idmap.size());
}
write_graphviz(out, graph_,
boost::make_label_writer(
boost::make_assoc_property_map(vertexLabelMap_)),
boost::make_label_writer(
boost::make_assoc_property_map(edgeLabelMap_)),
boost::default_writer{},
boost::make_assoc_property_map(idmap));
}
private:
std::map<VertexDesc, std::string> vertexLabelMap_;
std::map<EdgeDesc, std::string> edgeLabelMap_;
std::map<std::string, VertexDesc> vertexMap_;
std::set<std::pair<std::string, std::string>> edgeSet_;
Graph graph_;
};
struct Node : Vertex {};
struct Arc : Edge {};
#include <iostream>
int main() {
MyGraph g;
const auto n1 = std::make_shared<Node>(Node{{"n1"}});
const auto n2 = std::make_shared<Node>(Node{{"n2"}});
const auto e1 = std::make_shared<Arc>(Arc{{"e1"}});
g.addEdge(n1, n2, e1);
g.print(std::cout);
}
Alternatively, if such an index can be made part of Vertex:
Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
struct Vertex {
int id;
std::string name;
};
struct Edge {
std::string name;
};
class MyGraph {
public:
MyGraph() = default;
using Graph = boost::adjacency_list< //
boost::setS, boost::listS, boost::bidirectionalS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
using VertexDesc = boost::graph_traits<Graph>::vertex_descriptor;
using EdgeDesc = boost::graph_traits<Graph>::edge_descriptor;
void addNode(std::shared_ptr<Vertex> const& node)
{
const auto name = node->name;
if (vertexMap_.find(name) == vertexMap_.end()) {
const auto vertex = add_vertex(node, graph_);
vertexLabelArray_.push_back(name);
vertexMap_[name] = vertex;
}
}
void addEdge(std::shared_ptr<Vertex> const& src,
std::shared_ptr<Vertex> const& dst,
std::shared_ptr<Edge> const& weight = nullptr)
{
const auto srcName = src->name;
const auto dstName = dst->name;
const auto vertexPair = std::make_pair(srcName, dstName);
if (edgeSet_.find(vertexPair) == edgeSet_.end()) {
addNode(src);
addNode(dst);
const auto edge = add_edge(vertexMap_[srcName], vertexMap_[dstName],
weight, graph_)
.first;
edgeSet_.insert(vertexPair);
edgeLabelMap_[edge] = weight ? weight->name : "";
}
}
void print(std::ostream& out)
{
auto idmap = boost::make_transform_value_property_map(
[](auto const& sp) { return sp->id; },
get(boost::vertex_bundle, graph_));
write_graphviz(
out, graph_,
boost::make_label_writer(boost::make_iterator_property_map(
vertexLabelArray_.begin(), idmap)),
boost::make_label_writer(
boost::make_assoc_property_map(edgeLabelMap_)),
boost::default_writer{}, idmap);
}
private:
std::vector<std::string> vertexLabelArray_;
std::map<EdgeDesc, std::string> edgeLabelMap_;
std::map<std::string, VertexDesc> vertexMap_;
std::set<std::pair<std::string, std::string>> edgeSet_;
Graph graph_;
};
struct Node : Vertex {};
struct Arc : Edge {};
#include <iostream>
int main() {
MyGraph g;
const auto n1 = std::make_shared<Node>(Node{{0, "n1"}});
const auto n2 = std::make_shared<Node>(Node{{1, "n2"}});
const auto e1 = std::make_shared<Arc>(Arc{{"e1"}});
g.addEdge(n1, n2, e1);
g.print(std::cout);
}
Q.: 2 - i am using a shared_ptr for allowing for custom object to be
attached to vertices and edges.
I noticed this. I think it's a little bit questionable. If you're looking for efficiency, I wouldn't be using shared_ptr all over the place.
Q.: is there an easy way for looking up a
vertex index based on its attached object?
Not built in. There is the (undocumented?) labeled_graph adaptor that has... some convenience. YMMV. Also, you can use a bimap or similar.
Q.: 3 - Is it possible to remove most of the external data structures and
use boost property instead somehow?
I would strongly consider this. Of course you can. Basically, you already do this.
Review
Your code has many opportunities for improvement (including effective optimizations).
pass the shared pointers by const&:
do not pass shared pointers in the first place, since you're not intending to share ownership/observe refcounts/lifetimes. I'll show this when I show a version that embodies the node/edge data in the graph model directly.
void addNode could return the vertex descriptor, avoiding a lot of redundant lookups. This also makes it more explicit about an item already existing not being an error. (Right now it simply ignores the addNode/addEdge?)
void ensureEdge(std::shared_ptr<Vertex> const& src,
std::shared_ptr<Vertex> const& dst,
std::shared_ptr<Edge> const& edge = {})
{
if (edgeSet_.emplace(src->name, dst->name).second) {
auto [descriptor, isnew] = add_edge( //
ensureNode(src), ensureNode(dst), edge, graph_);
edgeLabelMap_.emplace(descriptor, edge ? edge->name : "");
}
}
Doing less work means being more efficient.
Like you almost mentioned you can already test whether an edge exists using the result of add_edge, obviating the extra edgeSet_:
EdgeDesc ensureEdge(std::shared_ptr<Vertex> const& src,
std::shared_ptr<Vertex> const& dst,
std::shared_ptr<Edge> const& edge = {})
{
auto [descriptor, isnew] =
add_edge(ensureNode(src), ensureNode(dst), edge, graph_);
if (isnew)
edgeLabelMap_.emplace(descriptor, edge ? edge->name : "");
return descriptor;
}
Note that now, because the behaviour is more consistent, we can also return the edge descriptor. This will remove the need to query for the edge we just added.
Current version Live On Compiler Explorer:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
struct Vertex { std::string name; };
struct Edge { std::string name; };
class MyGraph {
public:
MyGraph() = default;
using Graph = boost::adjacency_list< //
boost::setS, boost::vecS, boost::bidirectionalS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
using VertexDesc = Graph::vertex_descriptor;
using EdgeDesc = Graph::edge_descriptor;
VertexDesc ensureNode(std::shared_ptr<Vertex> const& node)
{
auto const& name = node->name;
auto it = vertexMap_.find(name);
if (it == vertexMap_.end()) {
auto descriptor = add_vertex(node, graph_);
vertexLabelArray_.push_back(name);
vertexMap_[name] = descriptor;
it = vertexMap_.emplace(name, descriptor).first;
}
return it->second;
}
EdgeDesc ensureEdge(std::shared_ptr<Vertex> const& src,
std::shared_ptr<Vertex> const& dst,
std::shared_ptr<Edge> const& edge = {})
{
auto [descriptor, isnew] =
add_edge(ensureNode(src), ensureNode(dst), edge, graph_);
if (isnew)
edgeLabelMap_.emplace(descriptor, edge ? edge->name : "");
return descriptor;
}
void print(std::ostream& out) const
{
write_graphviz(
out, graph_, //
boost::make_label_writer(vertexLabelArray_.data()),
boost::make_label_writer(make_assoc_property_map(edgeLabelMap_)));
}
private:
std::vector<std::string> vertexLabelArray_;
std::map<EdgeDesc, std::string> edgeLabelMap_;
std::map<std::string, VertexDesc> vertexMap_;
Graph graph_;
};
struct Node : Vertex {};
struct Arc : Edge {};
#include <iostream>
int main() {
MyGraph g;
const auto n1 = std::make_shared<Node>(Node{{"n1"}});
const auto n2 = std::make_shared<Node>(Node{{"n2"}});
const auto e1 = std::make_shared<Arc>(Arc{{"e1"}});
g.ensureEdge(n1, n2, e1);
g.print(std::cout);
}
Still prints
digraph G {
0[label=n2];
1[label=n1];
1->0 [label=e1];
}
Other Steps
You specified BidirectionalGraph support, but don't currently use the in-edge interface. Consider dropping that so you don't incur the overhead for maintaining the redundant edge information
using Graph = boost::adjacency_list< //
boost::setS, boost::vecS, boost::directedS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
Consider using value semantics for the property bundles. This will reduce allocations, increase locality of reference and may enable a host of storage optimization opportunities.
Consider this version, which no longer uses vertexLabelArray_ nor edgeLabelMap_ (both properties simply reside inside the graph model after all):
void print(std::ostream& out) const {
write_graphviz(out, graph_,
make_label_writer(get(&Vertex::name, graph_)),
make_label_writer(get(&Edge::name, graph_)));
}
Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
struct Vertex { std::string name; };
struct Edge { std::string name; };
class MyGraph {
public:
using Graph = boost::adjacency_list< //
boost::setS, boost::vecS, boost::directedS, Vertex, Edge>;
using VertexDesc = Graph::vertex_descriptor;
using EdgeDesc = Graph::edge_descriptor;
VertexDesc ensureNode(Vertex const& node) {
if (auto it = vertexMap_.find(node.name); it != vertexMap_.end())
return it->second;
auto descriptor = add_vertex(node, graph_);
vertexMap_[node.name] = descriptor;
vertexMap_.emplace(node.name, descriptor);
return descriptor;
}
EdgeDesc ensureEdge(Vertex const& src, Vertex const& dst, Edge edge) {
auto [descriptor, isnew] =
add_edge(ensureNode(src), ensureNode(dst), std::move(edge), graph_);
return descriptor; // TODO maybe raise an error if !isnew?
}
void print(std::ostream& out) const {
write_graphviz(out, graph_,
make_label_writer(get(&Vertex::name, graph_)),
make_label_writer(get(&Edge::name, graph_)));
}
private:
std::map<std::string, VertexDesc> vertexMap_;
Graph graph_;
};
struct Node : Vertex { };
struct Arc : Edge { };
#include <iostream>
int main() {
MyGraph g;
Node n1{{"n1"}}, n2{{"n2"}};
Arc e1{{"e1"}};
g.ensureEdge(n1, n2, e1);
g.ensureEdge({"n3"}, {"n4"}, {"e2"});
g.print(std::cout);
}
Prints
digraph G {
0[label=n2];
1[label=n1];
2[label=n4];
3[label=n3];
1->0 [label=e1];
3->2 [label=e2];
}
Much More Efficient Vertex Lookup?
If you really care about storage and lookup efficiency, make vertexMap_ key a string_view. This requires careful thought about the lifetime of the referred-to strings.
// use stored property, not parameter as vertexMap_ key!
vertexMap_.emplace(graph_[vd].name, vd);
For starters, you NEED a container selector with reference stabiility for the vertex container (e.g. listS).
Also, consider making the container a flat_map so storage and lookup will be much optimized. They will effectively become vector<tuple<char const*, size_t, size_t> >:
boost::container::flat_map<std::string_view, VertexDesc> vertexMap_;
See It Live On Compiler Explorer
#include <boost/container/flat_map.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
struct Vertex { std::string name; };
struct Edge { std::string name; };
class MyGraph {
public:
using Graph = boost::adjacency_list< //
boost::setS, boost::listS, boost::directedS, Vertex, Edge>;
using VertexDesc = Graph::vertex_descriptor;
using EdgeDesc = Graph::edge_descriptor;
VertexDesc ensureNode(Vertex const& node) {
if (auto it = vertexMap_.find(node.name); it != vertexMap_.end())
return it->second;
auto vd = add_vertex(node, graph_);
// use stored property, not parameter as vertexMap_ key!
vertexMap_.emplace(graph_[vd].name, vd);
return vd;
}
EdgeDesc ensureEdge(Vertex const& src, Vertex const& dst, Edge edge) {
auto [ed, isnew] =
add_edge(ensureNode(src), ensureNode(dst), std::move(edge), graph_);
return ed; // TODO maybe raise an error if !isnew?
}
void print(std::ostream& out) const {
write_graphviz(out, graph_,
make_label_writer(get(&Vertex::name, graph_)),
make_label_writer(get(&Edge::name, graph_)),
boost::default_writer{},
get(&Vertex::name, graph_));
}
private:
boost::container::flat_map<std::string_view, VertexDesc> vertexMap_;
Graph graph_;
};
struct Node : Vertex { };
struct Arc : Edge { };
#include <iostream>
int main() {
MyGraph g;
Node n1{{"n1"}}, n2{{"n2"}};
Arc e1{{"e1"}};
g.ensureEdge(n1, n2, e1);
g.ensureEdge({"n3"}, {"n4"}, {"e2"});
g.print(std::cout);
}
Note this cheated a little by using the Vertex name as the graphviz node ID. That makes a lot of sense anyways:
digraph G {
n2[label=n2];
n1[label=n1];
n4[label=n4];
n3[label=n3];
n1->n2 [label=e1];
n3->n4 [label=e2];
}
If you insist on integral IDs - you can hack them back using the fact that vertexMap_ is now contiguous:
void print(std::ostream& out) const {
auto node_id = boost::make_function_property_map<VertexDesc>(
[this](VertexDesc vd) {
return vertexMap_.find(graph_[vd].name) - vertexMap_.begin();
});
write_graphviz(out, graph_,
make_label_writer(get(&Vertex::name, graph_)),
make_label_writer(get(&Edge::name, graph_)),
boost::default_writer{}, node_id);
}
This prints (Live Link):
digraph G {
1[label=n2];
0[label=n1];
3[label=n4];
2[label=n3];
0->1 [label=e1];
2->3 [label=e2];
}
Note that doing the listS vertex container selection makes a BIG difference in resource trade-offs in the graph implementation itself, so if the vertex lookup via vertexMap_ is not your bottleneck, don't do this optimization!
More BGL facilities
As I mentioned there's some supported for labeled graphs in BGL. I'm not recommending that as I've run into my share of implementation bugs/quirks. But I would be remiss not to mention it here.
Also see How to create a named_graph with Boost Graph Library? which is a very good answer

c++ Prim minimum spanning tree using Boost Library

I am working on a project where I need to implement Dijkstra's, Prim's and A* algorithms on an input file my professor will provide. I have successfully written working code for Dijkstra's but I am hitting a wall trying to get my same graph to work for Prim's as well. I feel like my problem is not properly having a min spanning tree map to pull my info from but I cant really wrap my head around what the problem is, any help would be greatly appreciated.
structs and graph traits for edges and verts:
typedef boost::adjacency_list_traits<vecS, vecS, undirectedS, listS> GraphTraits;
// type 'Vertex' identifies each vertex uniquely:
typedef GraphTraits::vertex_descriptor Vertex;
// Property type associated to each vertex:
struct VertexProperty {
string name; // Name of vertex (i.e., "location")
Vertex predecessor; // Predecessor along optimal path.
double distance; // Distance to the goal, along shortest path.
default_color_type color; // for use by dijkstra.
VertexProperty(const string& aName = "") : name(aName) { };
};
// Property type associated to each edge:
struct EdgeProperty {
double weight; // distance to travel along this edge.
EdgeProperty(double aWeight = 0.0) : weight(aWeight) { };
};
// Type of the graph used:
typedef adjacency_list<vecS, vecS, undirectedS, VertexProperty, EdgeProperty> Graph;
// Create a global graph object 'g'
Graph g;
// This is a visitor for the dijkstra algorithm. This visitor does nothing special.
struct do_nothing_dijkstra_visitor {
template <typename Vertex, typename Graph>
void initialize_vertex(Vertex u, const Graph& g) const { };
template <typename Vertex, typename Graph>
void examine_vertex(Vertex u, const Graph& g) const { };
template <typename Edge, typename Graph>
void examine_edge(Edge e, const Graph& g) const { };
template <typename Vertex, typename Graph>
void discover_vertex(Vertex u, const Graph& g) const { };
template <typename Edge, typename Graph>
void edge_relaxed(Edge e, const Graph& g) const { };
template <typename Edge, typename Graph>
void edge_not_relaxed(Edge e, const Graph& g) const { };
template <typename Vertex, typename Graph>
void finish_vertex(Vertex u, const Graph& g) const { };
};
Variables:
string tempName1, tempName2, tempString, data2;
int weight;
string inputFile;
int choice;
Vertex cur_v, start_v, goal_v;
map<string, Vertex> name2v, name1v;
double totalDist, tempDist;
int numVert = 0;
Graph constructions based on file uploaded:
//build graph based on file loaded
getline(fin, tempString);
getline(fin, tempString);
stringstream tempSS(tempString);
while (getline(tempSS, tempName1, ',')) {
name2v[tempName1] = add_vertex(VertexProperty(tempName1), g);
numVert++;
}
getline(fin, tempString);
while (getline(fin, tempString)) {
tempString.erase(tempString.begin(), tempString.begin() + tempString.find('(') + 1);
tempString.erase(tempString.begin() + tempString.find(')'), tempString.end());
stringstream temp_ss(tempString);
getline(temp_ss, tempName1, ',');
getline(temp_ss, tempName2, ',');
temp_ss >> weight;
add_edge(name2v[tempName1], name2v[tempName2], EdgeProperty(weight), g);
}
name1v = name2v;
Prim's call:
cout << "Please enter the Vertex you would like to start at: ";
cin >> tempName1;
transform(tempName1.begin(), tempName1.end(), tempName1.begin(), ::toupper);
start_v = name1v[tempName1];
prim_minimum_spanning_tree(g, start_v,
get(&VertexProperty::predecessor, g),
get(&VertexProperty::distance, g),
get(&EdgeProperty::weight, g),
identity_property_map(),
do_nothing_dijkstra_visitor());
I tried to just include the code that matters. Like I said this code will work for Dijkstra but I am not sure how to make it work for Prim's. I am thinking I need to add more to the struct for the VertexProperty or have a map to store the min spannning tree. Thanks in advance.
I don't see what exactly you are asking (aside from code style and quality issues).
Here it is, note
the reduced visitor
removed confusing comments
and of course that I'm generating a random graph here because we don't have your input
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/prim_minimum_spanning_tree.hpp>
#include <boost/graph/random.hpp>
#include <fstream>
#include <map>
#include <random>
#include <sstream>
typedef boost::adjacency_list_traits<boost::vecS, boost::vecS, boost::undirectedS> GraphTraits;
typedef GraphTraits::vertex_descriptor Vertex;
struct DijkstraStuff {
Vertex predecessor;
double distance;
boost::default_color_type color; // for use by dijkstra.
};
struct VertexProperty : DijkstraStuff {
std::string name;
VertexProperty(const std::string &aName = "") : name(aName){};
};
struct EdgeProperty {
double weight; // distance to travel along this edge.
EdgeProperty(double aWeight = 0.0) : weight(aWeight){};
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexProperty, EdgeProperty> Graph;
struct do_nothing_dijkstra_visitor : boost::default_dijkstra_visitor {};
int main() {
Graph g;
std::map<std::string, Vertex> name_map;
// read graph (random for now)
{
std::mt19937 prng{ 42 };
generate_random_graph(g, 10, 20, prng);
for (auto vd : boost::make_iterator_range(vertices(g))) {
name_map[g[vd].name = "NAME" + std::to_string(vd)] = vd;
}
std::uniform_real_distribution<double> weight_dist(0, 1);
for (auto ed : boost::make_iterator_range(edges(g))) {
g[ed].weight = weight_dist(prng);
}
}
print_graph(g, get(&VertexProperty::name, g));
Graph::vertex_descriptor start_v;
std::cout << "Please enter the Vertex you would like to start at: ";
{
std::string startName;
std::cin >> startName;
std::transform(startName.begin(), startName.end(), startName.begin(),
[](uint8_t ch) { return std::toupper(ch); });
start_v = name_map.at(startName);
}
boost::prim_minimum_spanning_tree(g, start_v, get(&VertexProperty::predecessor, g),
get(&VertexProperty::distance, g), get(&EdgeProperty::weight, g),
boost::identity_property_map(), do_nothing_dijkstra_visitor());
}
Printing the result
The resulting MST is encoded as the predecessor map. Print it as follows:
for (auto vd : boost::make_iterator_range(vertices(g))) {
auto p = g[vd].predecessor;
std::cout << "Pred of " << g[vd].name << " is " << g[p].name << "\n";
}
(This assumes all vertices are in the MST, for simplicity. This is the same as assuming you didn't have unconnected vertices in the input)
Prints (for the given random seed and starting vertex):
Pred of NAME1 is NAME9
Pred of NAME2 is NAME2
Pred of NAME3 is NAME6
Pred of NAME4 is NAME1
Pred of NAME5 is NAME6
Pred of NAME6 is NAME1
Pred of NAME7 is NAME3
Pred of NAME8 is NAME7
Pred of NAME9 is NAME0

boost::astar_search on filtered graph

I need to run A* on a graph with some of the edges removed. To do so I construct a filtered graph with blacklisted edges and I want to run A* on the filtered graph. The blacklisted edges are embedded in the class BlackListEdgeConstraint, that I initialize by passing to its constructor the list of the forbidden edges. This BlackListEdgeConstraint is correctly constructed, and I construct the filtered graph with these constrain. The problem is that when I run A* on the filtered graph, another BlackListEdgeConstraint object is mysteriously constructed with an empty constructor, and no edge is in effect blacklisted. Why is it happening?
This is my complete code:
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/astar_search.hpp>
using namespace std;
typedef std::pair<int, int> Edge;
typedef boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, boost::property<boost::edge_weight_t, int> > graph_t;
typedef boost::graph_traits<graph_t>::vertex_descriptor vertex_descriptor;
struct BlackListEdgeConstraint
{
private:
std::set<Edge> blackList;
graph_t* g;
public:
BlackListEdgeConstraint():blackList(std::set<Edge>() ),g(NULL){throw std::runtime_error("This should not be called");};
BlackListEdgeConstraint(std::set<Edge>& list, graph_t* g_) : blackList(list), g(g_)
{
Edge edge = *blackList.begin();
std::cout<<"The black list contains "<< edge.first<<"-"<<edge.second<<std::endl;
}
/**
* This is the "predicate function" used by boost::filtered_graph (
* see http://www.boost.org/doc/libs/1_64_0/libs/graph/doc/filtered_graph.html )
* It it returns true, the edge is included in the filtered graph, otherwise it is excluded.
*/
bool operator()(const boost::graph_traits<graph_t>::edge_descriptor& e) const
{
Edge edge(source(e,*g), target(e,*g) );
std::cout<<"Should we include edge "<<source(e,*g)<<" ->"<< target(e,*g)<<" represented by a descriptor "<<e<<"? ";
//Include the edge if it's not in the blacklist.
bool include = (blackList.find( edge ) == blackList.end() );
std::cout<<include<<std::endl;
return include;
}
};
template<class GraphType>
class MyHeuristic : public boost::astar_heuristic<GraphType, double>
{
private:
const GraphType* g;
public:
MyHeuristic(const GraphType* g_): g(g_) {};
double operator()(vertex_descriptor v)
{
return 0;
}
};
//Used to terminate our search
struct GoalsReached{};
class MyVisitor : public boost::default_astar_visitor
{
private:
vertex_descriptor goal;
public:
MyVisitor(vertex_descriptor goal_): goal(goal_){};
template<class GraphType>
void examine_vertex(vertex_descriptor u, const GraphType& g)
{ if (u==goal) throw GoalsReached(); }
};
int main()
{
Edge edge_array[] = {Edge(0,1), Edge(1,2), Edge(2,3), Edge(3,0), Edge(1,3) };
int weights[] = {1,1,1,1,1};
int num_arcs = sizeof(edge_array) / sizeof(Edge);
int num_nodes = 4;
// Graph created from the list of edges
graph_t g(edge_array, edge_array + num_arcs, weights, num_nodes);
// Create descriptor for the source node
vertex_descriptor s = vertex(0, g);
vertex_descriptor goal = vertex(3,g) ;
std::set<Edge> blacklist; blacklist.insert( Edge(1,3) );
BlackListEdgeConstraint filter(blacklist, &g);
boost::filtered_graph<graph_t, BlackListEdgeConstraint> filtered(g, filter);
cout<<"filtered constructed"<<endl;
// Create vectors to store the predecessors (p) and the distances from the root (d)
std::vector<vertex_descriptor> p(num_vertices(filtered));
std::vector<int> d(num_vertices(filtered));
try{
cout<<"Launching astar_search"<<endl;
boost::astar_search(filtered, s,
MyHeuristic<boost::filtered_graph<graph_t, BlackListEdgeConstraint>>(&filtered),
boost::predecessor_map(&p[0]).
distance_map(&d[0]).
visitor(MyVisitor(goal) )
);
cout<<"astar_search launched"<<endl;
}catch(const GoalsReached& )
{ // Print the path
std::vector<boost::graph_traits<graph_t>::vertex_descriptor > path;
boost::graph_traits<graph_t>::vertex_descriptor current = goal;
while(current!=s)
{
path.push_back(current);
current = p[current];
}
path.push_back(s);
// Prints the path obtained in reverse
std::vector<boost::graph_traits<graph_t>::vertex_descriptor >::reverse_iterator it;
for (it = path.rbegin(); it != path.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
return EXIT_SUCCESS;
}
And this is the output:
The black list contains 1-3
filtered constructed
Launching astar_search
terminate called after throwing an instance of 'std::runtime_error'
what(): This should not be called
My boost version is 1.54
The problem is that, when boost::astar_search(..) is invoked, the empty constructor BlackListEdgeConstraint() is called.
I don't know how you reach the conclusion. I cannot reproduce that:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/astar_search.hpp>
struct VertexProperties {
};
struct EdgeProperties {
double weight = 1;
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexProperties, EdgeProperties> MyGraph;
struct StreetDirectory {
using Graph = MyGraph;
using Edge = Graph::edge_descriptor;
using Vertex = Graph::vertex_descriptor;
};
struct BlackListEdgeConstraint : StreetDirectory
{
private:
std::set<StreetDirectory::Edge> blackList;
public:
BlackListEdgeConstraint(const std::set<Edge>& list) : blackList(list) {};
BlackListEdgeConstraint()
{
throw std::runtime_error("This should not be called");
};
bool operator()(const Edge& e) const
{
//Include the edge if it's not in the blacklist.
return blackList.find(e) == blackList.end();
}
};
int main() {
MyGraph graph;
const std::set<StreetDirectory::Edge> blacklistEdges {
add_edge(1,2,graph).first,
add_edge(1,3,graph).first,
add_edge(2,4,graph).first,
};
add_edge(4,2,graph);
BlackListEdgeConstraint filter(blacklistEdges);
boost::filtered_graph<MyGraph, BlackListEdgeConstraint> filtered(graph, filter);
std::vector<StreetDirectory::Vertex> p(boost::num_vertices(filtered)); //Output variable
std::vector<double> d(boost::num_vertices(filtered)); //Output variable
boost::default_astar_visitor vis;
boost::astar_search(
filtered,
1,
[](auto /*vd*/) { return 1; },
boost::predecessor_map(&p[0]).
weight_map(boost::get(&EdgeProperties::weight, filtered)).
distance_map(&d[0]).
visitor(vis)
);
}
Notes
in general functors are passed by value in (standard) library algorithms. So you'd use std::reference_wrapper<BlackListEdgeConstraint> if you wanted to use the same instance. But like I said, I don't see it happening, so it's not a problem AFAICT
in your sample there seemed to be no edge-weight map indicated. I don't see how that should have compiled

How do I attach objects to the nodes and edges of a graph using the Boost Graph Library?

I would like to use the Boost Graph Library more effectively by attaching properly encapsulated classes to graph nodes & edges. I am not interested in attaching int's or POD struct's. Following suggestions on other StackOverFlow articles, I have developed the following sample app. Can anybody tell me the magic I need to sprinkle onto the EdgeInfo class to make this thing compile?
I am using Visual Studio 2010 with Boost 1.54.0.
//------------------------------------------------------------------------
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <iostream>
//------------------------------------------------------------------------
struct VertexInfo
{
struct Tag
{
typedef boost::vertex_property_tag kind;
static std::size_t const num; // ???
};
typedef boost::property<Tag, VertexInfo> Property;
};
std::size_t const VertexInfo::Tag::num = reinterpret_cast<std::size_t> (&VertexInfo::Tag::num);
//------------------------------------------------------------------------
class EdgeInfo
{
int _nWeight;
public:
int getWeight () const {return _nWeight;}
struct Tag
{
typedef boost::edge_property_tag kind;
static std::size_t const num; // ???
};
typedef boost::property<boost::edge_weight_t, int> Weight;
typedef boost::property<Tag, EdgeInfo> Property;
EdgeInfo (int nWeight = 9999) : _nWeight (nWeight) {}
};
std::size_t const EdgeInfo::Tag::num = reinterpret_cast<std::size_t> (&EdgeInfo::Tag::num);
//------------------------------------------------------------------------
typedef boost::property<boost::edge_weight_t, int> EdgeProperty;
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexInfo::Property, EdgeProperty> GraphWorking;
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexInfo::Property, EdgeInfo::Property> GraphBroken;
//------------------------------------------------------------------------
template<typename GraphType, typename EdgeType> void
dijkstra (GraphType g, EdgeType e)
{
typedef boost::graph_traits<GraphType>::vertex_descriptor VertexDesc;
typedef boost::graph_traits<GraphType>::edge_descriptor EdgeDesc;
VertexDesc u = add_vertex (g);
VertexDesc v = add_vertex (g);
std::pair<EdgeDesc, bool> result = add_edge (u, v, e, g);
std::vector<VertexDesc> vecParent (num_vertices (g), 0);
dijkstra_shortest_paths (g, u, boost::predecessor_map (&vecParent[0]));
}
//------------------------------------------------------------------------
int
main (int argc, char** argv)
{
#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300
std::cout << "Buy a new compiler\n";
#else
std::cout << "Your compiler is fine\n";
#endif
GraphWorking gWorking;
GraphBroken gBroken;
dijkstra (gWorking, 3);
dijkstra (gBroken, EdgeInfo (4));
}
//------------------------------------------------------------------------
When I run your code i get an error in numeric_limits that results from a distance map in dijkstra.
"
Error 1 error C2440: '' : cannot convert from 'int' to 'D' c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\limits 92
"
probably from this part of http://www.boost.org/doc/libs/1_55_0/boost/graph/dijkstra_shortest_paths.hpp
typedef typename property_traits<DistanceMap>::value_type D;
D inf = choose_param(get_param(params, distance_inf_t()),
(std::numeric_limits<D>::max)());
I think there may be an easier way to tie a real class for your nodes and edges. Its more trouble than its worth to create vertex and edge property classes that will provide all the needed tagged properties (index, weight, color, etc) needed for most boost algorihtms.
Don't forget Edge class != Edge property.
The edge class is really the graph_traits::edge_discriptor.
Properties are the data associated with each edge. Same for vertex.
I would use bundled properties and add a pointer to your class in each one.
Here is an example
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>
#include <iostream>
//Fancy Edge class
class EdgeData
{
int _data;
public:
EdgeData(){
_data=0;
}
EdgeData(int data){
_data= data;
}
void printHello(){
std::cout << "hello " << _data << std::endl;
}
};
//Fancy Vert class
class VertexData
{
int _data;
public:
VertexData(){
_data=0;
}
VertexData(int data){
_data= data;
}
void printHello(){
std::cout << "hello " << _data << std::endl;
}
};
//bundled properties
struct VertexProps
{
VertexData* data;
};
struct EdgeProps
{
size_t weight;
EdgeData* data;
};
//Graph
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
VertexProps,EdgeProps> Graph;
//helpers
//Vertex
typedef boost::graph_traits<Graph>::vertex_descriptor Vertex;
//Edge
typedef boost::graph_traits<Graph>::edge_descriptor Edge;
//------------------------------------------------------------------------
template<typename GraphType> void
templateFunction (GraphType g)
{
typedef boost::graph_traits<GraphType>::edge_iterator edge_iter;
std::pair<edge_iter, edge_iter> ep;
edge_iter ei, ei_end;
ep = edges(g);
ei_end = ep.second;
for (ei = ep.first; ei != ei_end; ++ei){
g[*ei].data->printHello();
}
}
//if you want to alter the graph use referenced &graph
template<typename GraphType,typename EdgePropType> void
templateFuctionProps(GraphType &g, EdgePropType e)
{
typedef boost::graph_traits<GraphType>::vertex_descriptor VertexDesc;
VertexDesc v = add_vertex(g);
VertexDesc u = add_vertex(g);
//add an edge with the Edge property
add_edge(v,u,e,g);
}
//------------------------------------------------------------------------
int
main (int argc, char** argv)
{
Graph g;
//vertex holder
std::vector<Vertex> verts;
//add some verts
for(size_t i = 0; i < 5; ++i){
Vertex v = add_vertex(g);
g[v].data = new VertexData(i%2);
verts.push_back(v);
}
//add some edges
for(size_t i = 0; i < 4; ++i){
std::pair<Edge,bool> p = add_edge(verts.at(i),verts.at(i+1),g);
Edge e = p.first;
g[e].data = new EdgeData(i%3);
g[e].weight = 5;
}
//iterate edges and call a class function
typedef boost::graph_traits<Graph>::edge_iterator edge_iter;
std::pair<edge_iter, edge_iter> ep;
edge_iter ei, ei_end;
ep = edges(g);
ei_end = ep.second;
for (ei = ep.first; ei != ei_end; ++ei){
g[*ei].data->printHello();
}
std::cout << "Iterate with template with template " << std::endl;
templateFunction(g);
//Use an edge property in a function
EdgeProps edgeProp;
edgeProp.weight = 5;
edgeProp.data = new EdgeData(150);
std::cout << "Modity graph with template function " << std::endl;
templateFuctionProps(g,edgeProp);
std::cout << "Iterate again with template" << std::endl;
templateFunction(g);
//getting the weight property
boost::property_map<Graph,size_t EdgeProps::*>::type w
= get(&EdgeProps::weight, g);
std::cout << "Print weights" << std::endl;
ep = edges(g);
ei_end = ep.second;
for (ei = ep.first; ei != ei_end; ++ei){
std::cout << w[*ei] << std::endl;
}
std::cin.get();
}
//------------------------------------------------------------------------
Also I see you are using vecS, meaning that both vectors and edges are stored as vectors with a fixed ordering.
You could just have a class that stores your Edge and Vertex classes with a pointer to the vertex map or edge map for the graph.
I don't know your goals for this project, but I would definitely have higher level classes than manage all of this boost stuff behind the scenes. Meaning storing classes in a vector with an index look up would be hidden and encapsulated from applications that want to use your nice graph class.