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.
Related
I'm new to boost::graph (and boost really). I want to use boost::filtered_graph many times on the same original graph, and use the write_graphviz function to let me visualise the results. I think my understanding must be off though because the following code isn't doing what I think it should: to output the same graph with print_graph and write_graphviz.
MWE (compiled with C++14, gcc 9.3 on Ubuntu 20.04; boost version 1.73):
#include <cstdio>
#include <fstream>
#include <iostream>
#include <boost/graph/copy.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>
using namespace std;
typedef boost::adjacency_list< boost::vecS, boost::vecS > Graph;
typedef boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<Graph>::vertex_iterator vertex_iterator;
template <typename GraphType>
struct uniform_random_vertex_filter
{
uniform_random_vertex_filter() : prob(1.0) {} // default constructor is required
uniform_random_vertex_filter(float p) : prob(p) {}
bool operator()(const typename boost::graph_traits<GraphType>::vertex_descriptor& v) const
{
return drand48() < prob; // randomly select some vertices
}
private:
float prob;
};
int main(int argn, char **argc) {
unsigned int n = 5;
ofstream of;
Graph g(n);
vertex_iterator vit, uit, vend;
// build a complete graph on n vertices (I'm sure there's a simpler command for this):
for (boost::tie(vit,vend) = vertices(g); vit != vend; ++vit) {
for (uit = vit; uit != vend; ++uit) {
if (uit == vit) { continue; }
add_edge(*vit, *uit, g);
}
}
std::cout << "Original graph (OriginalGraph.dot):" << std::endl;
boost::print_graph(g);
of.open("OriginalGraph.dot", std::ios::trunc);
boost::write_graphviz(of, g);
of.close();
uniform_random_vertex_filter<Graph> vfilter(0.5);
boost::filtered_graph<Graph, boost::keep_all, uniform_random_vertex_filter<Graph> >
filteredGraph(g, boost::keep_all(), vfilter);
std::cout << "Filtered graph -- random selection of vertices (RandomVertexSelection.dot):" << std::endl;
boost::print_graph(filteredGraph);
Graph F;
boost::copy_graph(filteredGraph,F);
of.open("RandomVertexSelection.dot", std::ios::trunc);
boost::write_graphviz(of, F);
of.close();
return 0;
}
Which produces this output:
> Debug/BoostGraphFilter
Original graph:
0 --> 1 2 3 4
1 --> 2 3 4
2 --> 3 4
3 --> 4
4 -->
Filtered graph -- random selection of vertices (RandomVertexSelection.dot):
0 --> 1 2 3 4
1 --> 2 3
2 --> 3
>
--- which is fine, but the dot files are:
> cat OriginalGraph.dot
digraph G {
0;
1;
2;
3;
4;
0->1 ;
0->2 ;
0->3 ;
0->4 ;
1->2 ;
1->3 ;
1->4 ;
2->3 ;
2->4 ;
3->4 ;
}
> cat RandomVertexSelection.dot
digraph G {
0;
1;
2;
}
Hence the filtered_graph that's printed isn't the same as that written to .dot file (which has lost all the edges in this case).
Can someone please help me understand what I've done wrong?
Your filter is random. And since you didn't retain any state to make it transparent or deterministic, the results are random. Simple as that.
Ironically, at the same time you managed to get completely deterministic results across runs because you fail to use random correctly (e.g. seeding the generator).
In your case, the simplest would be to copy before first use: Live On Coliru
using Graph = boost::adjacency_list<boost::vecS, boost::vecS>;
Graph make_complete_graph(size_t n);
void report(Graph const& g, std::string name);
struct uniform_random_vertex_filter {
float prob = 1.0f;
bool operator()(auto v) const {
return drand48() < prob;
}
};
int main() {
Graph g = make_complete_graph(5);
report(g, "OriginalGraph");
for (int pct = 30; pct < 100; pct+=10) {
Graph F;
boost::copy_graph(boost::filtered_graph( //
g, //
boost::keep_all{},
uniform_random_vertex_filter{pct / 100.0f}),
F);
report(F, "RandomVertexSelection_" + std::to_string(pct));
}
}
// build a complete graph on n vertices
Graph make_complete_graph(size_t n)
{
Graph g(n);
for (auto [vit, vend] = vertices(g); vit != vend; ++vit) {
for (auto uit = vit; uit != vend; ++uit) {
if (uit != vit)
add_edge(*vit, *uit, g);
}
}
return g;
}
void report(Graph const& g, std::string name) {
boost::print_graph(g, std::cout << name << ":");
std::ofstream of(name + ".dot");
boost::write_graphviz(of, g);
}
If you want a stable random "cut" of a graph, make the filter stateful. This could be handy e.g. if you have a graph too large to copy.
Fixing The Random
As a sidenote, fixing the random to actually be seeded and reliably uniform:
Live On Coliru
#include <boost/graph/copy.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>
#include <fstream>
#include <iostream>
#include <random>
using Graph = boost::adjacency_list<boost::vecS, boost::vecS>;
using Filter = std::function<bool(Graph::vertex_descriptor)>;
Graph make_complete_graph(size_t n);
void report(Graph const& g, std::string name);
int main() {
Graph g = make_complete_graph(5);
report(g, "OriginalGraph");
std::mt19937 urbg{std::random_device{}()};
for (int pct = 30; pct < 100; pct += 10) {
Graph F;
std::bernoulli_distribution dist(pct / 100.);
boost::copy_graph(
boost::filtered_graph(g, boost::keep_all{},
Filter([&](auto) { return dist(urbg); })),
F);
report(F, "RandomVertexSelection_" + std::to_string(pct));
}
}
// build a complete graph on n vertices
Graph make_complete_graph(size_t n)
{
Graph g(n);
for (auto [vit, vend] = vertices(g); vit != vend; ++vit) {
for (auto uit = vit; uit != vend; ++uit) {
if (uit != vit)
add_edge(*vit, *uit, g);
}
}
return g;
}
void report(Graph const& g, std::string name) {
boost::print_graph(g, std::cout << name << ":");
std::ofstream of(name + ".dot");
boost::write_graphviz(of, g);
}
Now prints different random cuts every run.
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
I have a couple of boiler-plate functions I would like to replace with a template. They look roughly like:
std::vector<double> generate_means(
std::mt19937& g, unsigned int N,
double lower = -1.0, double upper = 1.0
) {
std::uniform_real_distribution<double> dist(lower, upper);
std::function<double()> rng = std::bind(dist, g);
std::vector<double> res(N);
std::generate(std::begin(res), std::end(res), gen);
return res;
}
The elements that need abstraction are return type (only contained type, always vector is fine) the arguments after N (e.g., lower and upper in this case) and the distribution (e.g., std::uniform_real_distribution).
What I'd like roughly to be able write:
auto generate_means = generate_template<
double, // results vector<double>
std::uniform_real_distribution, // uses uniform distro
double=-1.0,double=1.0 // with default args
>
auto generate_norm_deviates = generate_template<
double, // still provides vector<double>
std::normal_distribution, // different distro
double=0, double=1.0 // different defaults
>
auto generate_category_ids = generate_template<
unsigned int,
std::uniform_int_distribution,
unsigned int=0, unsigned int // again with two args, but only one default
>
I have some sub-pieces
template <class NUMERIC>
using generator = std::function<NUMERIC()>;
template <class NUMERIC>
std::vector<NUMERIC> series(unsigned int length, generator<NUMERIC> gen) {
std::vector<NUMERIC> res(length);
std::generate(std::begin(res), std::end(res), gen);
return res;
};
but when I try assembling like, for example
template <class NUMERIC, class DIST, class...Args>
std::vector<NUMERIC> generator_template(
std::mt19937& g, unsigned int N,
Args... args
) {
DIST<NUMERIC> dist(&args...);
generator<NUMERIC> gen = std::bind(dist, g);
return series(N, gen);
}
I run into compile errors (in this case error: expected unqualified-id). Is what I'd like approximately achievable? Is this approach in the right direction, or do I need do something fundamentally different? If it is in the right direction, what am I missing?
EDIT:
For application constraints: I'd like to be able to declare the generators with defaults for arguments, but I do need to occasionally use them without the defaults. Not having defaults is just inconvenient, however, not fatal. Example:
//... assorted calculations...
auto xmeans = generate_means(rng, 100); // x on (-1,1);
auto ymeans = generate_means(rng, 100); // y on (-1,1);
auto zmeans = generate_means(rng, 100, 0, 1); // z on (0,1);
It is impossible to have floating point number as template parameter.
However, you can do something as follow :
#include <random>
#include <limits>
#include <algorithm>
#include <vector>
#include <iostream>
template<typename T, template<typename> typename Distribution>
auto generate_random_template(T min = std::numeric_limits<T>::lowest(),
T max = std::numeric_limits<T>::max()) {
return [distribution = Distribution<double>{min, max}]
(auto &&generator, std::size_t number) mutable {
std::vector<T> result;
result.reserve(number);
auto generate = [&](){return distribution(generator);};
std::generate_n(std::back_inserter(result), number, generate);
return result;
};
}
int main() {
auto generate_means = generate_random_template<double, std::uniform_real_distribution>(0.0, 1.0);
std::mt19937 g;
std::vector<double> randoms = generate_means(g, 10);
for(auto r : randoms) std::cout << r << std::endl;
return 0;
}
EDIT: Use generate_n instead of generate for performances reasons
EDIT2 : If you want to use default parameters like you did for x, y, and z, you can also do something like that :
#include <random>
#include <limits>
#include <algorithm>
#include <vector>
#include <iostream>
template<typename T, template<typename> typename Distribution>
auto generate_random_template(T min = std::numeric_limits<T>::lowest(),
T max = std::numeric_limits<T>::max()) {
return [distribution = Distribution<double>{min, max}, min, max]
(auto &&generator, std::size_t number, auto ...args) mutable {
std::vector<T> result;
result.reserve(number);
if constexpr(sizeof...(args) > 0)
distribution.param(typename Distribution<T>::param_type(args...));
else
distribution.param(typename Distribution<T>::param_type(min, max));
auto generate = [&](){return distribution(generator);};
std::generate_n(std::back_inserter(result), number, generate);
return result;
};
}
int main() {
auto generate_means = generate_random_template<double, std::uniform_real_distribution>(-1.0, 1.0);
std::mt19937 g;
// x and y are between -1 and 1
std::vector<double> x = generate_means(g, 10);
std::vector<double> y = generate_means(g, 10);
std::vector<double> z = generate_means(g, 10, 0.0, 1.0); // z is between 0 and 1
for(int i = 0; i < 10; ++i) {
std::cout << x[i] << "," << y[i] << "," << z[i] << std::endl;
}
return 0;
}
Thanks assorted commenters, this is now working (when added to working blocks from Q):
template <class NUMERIC, template<class> class DIST, class ... Args>
std::vector<NUMERIC> generator_template(
std::mt19937& g, unsigned int N,
Args &&... args
) {
DIST<NUMERIC> dist(std::forward<Args>(args)...);
generator<NUMERIC> gen = std::bind(dist, g);
return series(N, gen);
};
auto generate_test = generator_template<double, std::uniform_real_distribution, double, double>;
Happy to see other answers, however - still trying to understand C++ template syntax generally, and would prefer a version that let's me set default arguments.
I'd be tempted to just accept a constructed distribution object.
template <typename Dist, typename URBG>
std::vector<typename Dist::value_type> generate(Dist&& dist, URBG&& gen, std::size_t N)
{
std::vector<typename Dist::value_type> res(N);
std::generate(res.begin(), res.end(), std::bind(std::forward<Dist>(dist), std::forward<URBG>(gen)));
return res;
}
This is related to a question I had yesterday about accessing vertices using integer indices. That thread is here: Accessing specific vertices in boost::graph
The solution there indicated that using vecS as the type for vertices, it is indeed possible to access specific vertices using the integer index. I was wondering if there is a similar method provided by boost to access arbitrary edges efficiently using integer indices.
Attached is a code that depicts the former (valid access of vertices with integer indices) and accessing the edges based on the developer explicitly maintaining two arrays, from[] and to[], that store the source and the target, respectively of the edges.
The code creates the following graph:
#include <boost/config.hpp>
#include <iostream>
#include <fstream>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
using namespace boost;
typedef adjacency_list_traits<vecS, vecS, directedS> Traits;
typedef adjacency_list<
vecS, vecS, directedS,
property<
vertex_name_t, std::string,
property<vertex_index_t, int,
property<vertex_color_t, boost::default_color_type,
property<vertex_distance_t, double,
property<vertex_predecessor_t, Traits::edge_descriptor> > > > >,
property<
edge_index_t, int,
property<edge_capacity_t, double,
property<edge_weight_t, double,
property<edge_residual_capacity_t, double,
property<edge_reverse_t, Traits::edge_descriptor> > > > > >
Graph;
int main() {
int nonodes = 4;
const int maxnoedges = 4;//I want to avoid using this.
Graph g(nonodes);
property_map<Graph, edge_index_t>::type E = get(edge_index, g);
int from[maxnoedges], to[maxnoedges];//I want to avoid using this.
// Create edges
Traits::edge_descriptor ed;
int eindex = 0;
ed = (add_edge(0, 1, g)).first;
from[eindex] = 0; to[eindex] = 1;//I want to avoid using this.
E[ed] = eindex++;
ed = (add_edge(0, 2, g)).first;
from[eindex] = 0; to[eindex] = 2;//I want to avoid using this.
E[ed] = eindex++;
ed = (add_edge(1, 3, g)).first;
from[eindex] = 1; to[eindex] = 3;//I want to avoid using this.
E[ed] = eindex++;
ed = (add_edge(2, 3, g)).first;
from[eindex] = 2; to[eindex] = 3;//I want to avoid using this.
E[ed] = eindex++;
graph_traits < Graph >::out_edge_iterator ei, e_end;
for (int vindex = 0; vindex < num_vertices(g); vindex++) {
printf("Number of outedges for vertex %d is %d\n", vindex, out_degree(vindex, g));
for (tie(ei, e_end) = out_edges(vindex, g); ei != e_end; ++ei)
printf("From %d to %d\n", source(*ei, g), target(*ei, g));
}
printf("Number of edges is %d\n", num_edges(g));
//Is there any efficient method boost provides
//in lieu of having to explicitly maintain from and to arrays
//on part of the developer?
for (int eindex = 0; eindex < num_edges(g); eindex++)
printf("Edge %d is from %d to %d\n", eindex, from[eindex], to[eindex]);
}
The code builds and compiles without error. The for loop with vindex works fine with out_edges and out_degree working fine taking as parameters integer indices.
Is there a way to do likewise for the next for loop that prints the edges using boost::graph data structures directly?
I looked at the following thread dealing with a similar question:
Boost graph library: Get edge_descriptor or access edge by index of type int
The suggested answer there was to use an unordered_map. Is there any tradeoff in using this as opposed to having the from[] and to[] arrays? Are there any other computationally efficient methods of accessing edges?
You can only do this if you
use a different graph model
an external edge index
Concepts
You could be interested in the AdjacencyMatrix concept. It doesn't exactly sport integral edge ids, but AdjacencyMatrix has lookup of edge by source/target vertices as well.
To get truly integral edge descriptors, you'd probably need write your own graph model class (modeling a set of existing BGL concepts). You might also be interested in grid_graph<> (which has a fixed set of numbered edges per vertex, where the vertices are a grid).
How to access edge_descriptor with given vertex_descriptor in boost::grid_graph - you could devise a "global" numering scheme and thus get linear lookup time
Adjacency List
Here's a modification from the previous answer showing an external index. It's akin to your solution. I chose bimap so at least you get the reverse lookup "automagically".
// Create edges
boost::bimaps::bimap<int, Graph::edge_descriptor> edge_idx;
auto new_edge_pair = [&,edge_id=0](int from, int to) mutable {
auto single = [&](int from, int to) {
auto d = add_edge(from, to, EdgeProperty { edge_id, 4 }, g).first;
if (!edge_idx.insert({edge_id++, d}).second)
throw std::invalid_argument("duplicate key");
return d;
};
auto a = single(from, to), b = single(to, from);
rev[a] = b;
rev[b] = a;
};
new_edge_pair(0, 1);
new_edge_pair(0, 2);
new_edge_pair(1, 3);
new_edge_pair(2, 3);
Now you can do the loop by edge id:
auto& by_id = edge_idx.left;
for (auto const& e : by_id) {
std::cout << "Edge #" << e.first << " is (" << source(e.second, g) << " -> " << target(e.second, g) << ")\n";
}
You can directly lookup an edge by it's id:
auto ed = by_id.at(random);
std::cout << "Random edge #" << random << " is (" << source(ed, g) << " -> " << target(ed, g) << ")\n";
The reverse lookup is a bit redundant, because you can do the same using BGL quite easily:
std::cout << "Reverse lookup: " << by_desc.at(ed) << "\n"; // reverse, though not very spectacular
std::cout << "Classic property lookup: " << g[ed].id << "\n"; // because it can be done using boost easily
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
#include <functional>
#include <iostream>
#include <boost/bimap.hpp>
#include <random>
std::mt19937 prng { std::random_device{}() };
using namespace boost;
struct VertexProperty { std::string name; };
struct EdgeProperty {
int id;
double capacity, residual_capacity;
EdgeProperty(int id, double cap, double res = 0)
: id(id), capacity(cap), residual_capacity(res)
{ }
};
typedef adjacency_list<vecS, vecS, directedS, VertexProperty, EdgeProperty> Graph;
int main() {
int nonodes = 4;
Graph g(nonodes);
// reverse edge map
auto rev = make_vector_property_map<Graph::edge_descriptor>(get(&EdgeProperty::id, g));
// Create edges
boost::bimaps::bimap<int, Graph::edge_descriptor> edge_idx;
auto new_edge_pair = [&,edge_id=0](int from, int to) mutable {
auto single = [&](int from, int to) {
auto d = add_edge(from, to, EdgeProperty { edge_id, 4 }, g).first;
if (!edge_idx.insert({edge_id++, d}).second)
throw std::invalid_argument("duplicate key");
return d;
};
auto a = single(from, to), b = single(to, from);
rev[a] = b;
rev[b] = a;
};
new_edge_pair(0, 1);
new_edge_pair(0, 2);
new_edge_pair(1, 3);
new_edge_pair(2, 3);
// property maps
struct VertexEx {
default_color_type color;
double distance;
Graph::edge_descriptor pred;
};
auto idx = get(vertex_index, g);
auto vex = make_vector_property_map<VertexEx>(idx);
auto pred = make_transform_value_property_map(std::mem_fn(&VertexEx::pred), vex);
auto color = make_transform_value_property_map(std::mem_fn(&VertexEx::color), vex);
auto dist = make_transform_value_property_map(std::mem_fn(&VertexEx::distance), vex);
auto cap = get(&EdgeProperty::capacity, g);
auto rescap = get(&EdgeProperty::residual_capacity, g);
// algorithm
double flow = boykov_kolmogorov_max_flow(g, cap, rescap, rev, pred, color, dist, idx, 0, 3);
std::cout << "Flow: " << flow << "\n";
{
auto& by_id = edge_idx.left;
auto& by_desc = edge_idx.right;
for (auto const& e : edge_idx.left) {
std::cout << "Edge #" << e.first << " is (" << source(e.second, g) << " -> " << target(e.second, g) << ")\n";
}
int random = prng() % num_edges(g);
auto ed = by_id.at(random);
std::cout << "Random edge #" << random << " is (" << source(ed, g) << " -> " << target(ed, g) << ")\n";
std::cout << "Reverse lookup: " << by_desc.at(ed) << "\n"; // reverse, though not very spectacular
std::cout << "Classic property lookup: " << g[ed].id << "\n"; // because it can be done using boost easily
}
}
Printing
Flow: 8
Edge #0 is (0 -> 1)
Edge #1 is (1 -> 0)
Edge #2 is (0 -> 2)
Edge #3 is (2 -> 0)
Edge #4 is (1 -> 3)
Edge #5 is (3 -> 1)
Edge #6 is (2 -> 3)
Edge #7 is (3 -> 2)
Random edge #2 is (0 -> 2)
Reverse lookup: 2
Classic property lookup: 2
Adjacency Matrix
Keeps everything the same, except for changing the model:
#include <boost/graph/adjacency_matrix.hpp>
typedef adjacency_matrix<directedS, VertexProperty, EdgeProperty> Graph;
And now you get the added capability of lookup by vertices:
Live On Coliru
std::cout << "Finding (3, 1) results in Edge #" << by_desc.at(edge(3, 1, g).first) << "\n";
Prints
Finding (3, 1) results in Edge #5
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.