Create Edges in Boost Graph using Multi Threading - boost
I am trying to create a boost graph with more than 50K nodes (It will map the configuration space of a robot) and I want to create edges between the node using multi threading as it has become a bottleneck for my program.
I store all the vertices' index in a hash map so that they are easy for lookup while adding edges. For each vertex I find 5 nearest neighbors that are to be connected.
Also I have disabled parallel edges in the graph and the graph definition is
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS, VertexProperties, EdgeProperties>;
For finding the nearest neighbours, I use Local Senstivity Hashing (github_repo).
model* myModel;
myModel = new lshEuclidean();
myModel->fit(datapoints, status); /// training on all leaf nodes that are not in collision
Also before connecting the edges, I insert all the vertices in the graph and also make a hash map so that it is easy to recover the vertex index for adding an edge. (For quick testing, I convert the vector to a string to store in the hashmap, I know this is inefficient and need to make my own hash function)
BoostGraph::VertexProperties vp1;
BoostGraph graph(5);
std::unordered_map<std::string, int> map;
for(int center = 0; center < finalLeafNodes.size(); center++){
Vec origin = finalLeafNodes[center]->getOrigin();
std::vector<double> joint_angle = {origin.at(0)*toRadians, origin.at(1)*toRadians, origin.at(2)*toRadians,
origin.at(3)*toRadians, origin.at(4)*toRadians};
Eigen::VectorXd joint_angle_center;
joint_angle_center.resize(5);
joint_angle_center << joint_angle[0], joint_angle[1], joint_angle[2], joint_angle[3], joint_angle[4];
vp1.joint_angles = joint_angle;
BoostGraph::Vertex v_center = graph.AddVertex(vp1);
int vertex_index_center = graph.getVertexIndex(v_center);
Vec joint_angle_in_vector_degrees = origin;
std::stringstream output;
std::copy(joint_angle_in_vector_degrees.begin(), joint_angle_in_vector_degrees.end(), std::ostream_iterator<double>(output, " "));
map[output.str()] = vertex_index_center;
}
Then for each vertex node, I find neighbours in a given radius, sort them to nearest neighbour and take top 3/5 and add an edge by finding those neighbours vertex index through the hashmap mentioned above. I also have a local planner that checks if the path between two points will also be collision free or not. If its collision free, edge is added.
neighbors.sort([&query](Item &a, Item &b) -> bool {compare(a, b, query);});
auto edge = graph.AddEdge(center_iterator->second, neighbour_iterator->second, BoostGraph::EdgeProperties{(double)recursion_index + 1.});
Also I am now trying on a five degree of freedom robot, so the dimension has also increased.
I have tried multi threading with mutex_lock() but its not giving much of a speedup.
Is there a way to create a shared memory object where I can store the all the edges in multi threading and just loop over it to add the edges in the graph so that I don't have parallel edges.
I want to create edges between the node using multi threading as it has become a bottleneck for my program
Frequently the solution will be to change the choice of datastructure or algorithm. And it is quite likely that the time is actually spent doing other things than actually inserting edges.
In some cases you will even want to have a graph model that is just an edge list.
Here's a implementation of your description (using parts of the code from previous questions). In some sense it is straight-forward. In some sense it might show you some advanced algorithm/datastructure ideas. I think it doesn't have the performance bottleneck you are talking about?
Generating Input Data
Let's read vertices from CSV data. Generating 50k input lines:
Live On Coliru: gen.cpp
./a.out > input.txt; wc -l input.txt; tail input.txt
50000 input.txt
-0.54953,0.309816,1.49314
-1.38758,1.10754,1.12841
0.468204,-1.38628,1.29798
1.44672,-0.600287,-1.1688
1.28432,-1.40215,0.701882
1.4669,-0.215648,-0.404705
-0.701017,-0.130071,-0.62072
1.3742,-0.639261,1.44033
-1.17127,-1.48499,-1.03837
-1.16458,-1.19539,-0.946286
Parsing Vertices From Input Data
Note I included the optimization I suggested in an earlier question:
using JointAngles = std::array<double, 3>;
This also makes it easier later on to use geometry algorithms.
The parsing is not really related to the question, so posted as-is:
template <typename F> size_t read_vertices(std::string_view input, F callback) {
using namespace boost::spirit::x3;
using boost::fusion::at_c;
Vertex n = 0;
auto action = [&](auto& ctx) {
auto& vv = _attr(ctx);
callback(JointAngles{at_c<0>(vv), at_c<1>(vv), at_c<2>(vv)});
n += 1;
};
static auto const line = (double_ >> ',' >> double_ >> ',' >> double_)[action];
parse(begin(input), end(input), skip(blank)[line % (eol | eoi) > (*eol >> eoi)]);
return n;
}
Note how it is a whitespace tolerant where possible and supports ±inf/nan.
A Spatial Index
Instead of brute-forcing our way, let's use a Spatial Index from Boost Geometry. What this will allow us to do is find the nearest-k points much cheaper than bruteforce.
Firstly, include the relevant headers:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/adapted/std_array.hpp>
#include <boost/geometry/index/adaptors/query.hpp>
#include <boost/geometry/index/rtree.hpp>
Now, let's tell Boost Geometry about our point type, and define a Tree type:
BOOST_GEOMETRY_REGISTER_STD_ARRAY_CS(bg::cs::cartesian)
namespace bg = boost::geometry;
namespace bgi = bg::index;
using Tree = bgi::rtree<std::pair<JointAngles, Vertex>, bgi::rstar<16>>;
We choose R* packing algorithm, which should usually give us best nearest() performance at the cost of higher insertion cost:
Actually Read The Graph
Using the parsing function above, let's build the graph and the spatial index tree at once:
int main() {
// read and index vertices
Tree tree;
Graph graph;
std::ifstream ifs("input.txt", std::ios::binary);
std::string const input(std::istreambuf_iterator<char>(ifs), {});
graph.m_vertices.reserve(50'000);
auto const n = read_vertices(input, [&](JointAngles ja) {
tree.insert({ja, add_vertex(VertexProperties{ja}, graph)});
});
std::cout << "Parsed " << n << " vertices, indexed: " << tree.size()
<< " graph: " << num_vertices(graph) << "\n";
That's all. Note how each inserted point in the tree carries the vertex descriptor as meta data, so we can correlate vertices with tree nodes.
This code will print, as expected, for our generated input.txt:
Parsed 50000 vertices, indexed: 50000 graph: 50000
Adding 5-nearest edges
Using a bgi query this is pretty simple. Likely this can be optimized, but let's do the naive thing first, just to see whether the performance is reasonable:
// connect 5-degree nearest vertices
size_t added = 0, dups =0;
for (auto& [vja, v] : tree) {
for (auto& [uja, u] : tree | queried(bgi::nearest(vja, 6))) {
if (v == u)
continue;
auto w = bg::distance(vja, uja);
auto [e, ok] = add_edge(v, u, EdgeProperties{w}, graph);
//std::cout << (ok ? "Added " : "Duplicate ") << e << " weight " << w << "\n";
(ok? added:dups)++;
}
}
std::cout << "Total edges added:" << added << " dups:" << dups << "\n";
Note that we omit self-edges, and rely on setS and undirectedS to detect duplicates - which are obviously expected. This prints, for our test data:
Total edges added:150778 dups:99222
BONUS: A* search
Like in your previous question, let's perform an A* search between arbitrary vertices:
// do A* search
std::vector<Vertex> predecessors(n);
std::vector<double> distances(n);
auto vidx = get(boost::vertex_index, graph); // redundant with vecS
auto pmap = make_iterator_property_map(predecessors.data(), vidx);
auto dmap = make_iterator_property_map(distances.data(), vidx);
auto weightmap = get(&EdgeProperties::weight, graph);
std::mt19937 gen(std::random_device{}());
Vertex start = random_vertex(graph, gen);
Vertex goal = random_vertex(graph, gen);
try {
// call astar named parameter interface
auto heuristic = [&, gja = graph[goal].joint_angles](Vertex u) {
return bg::distance(graph[u].joint_angles, gja);
};
astar_search( //
graph, start, heuristic,
boost::predecessor_map(pmap) //
.distance_map(dmap)
.weight_map(weightmap)
.visitor(goal_visitor{goal}));
fmt::print("{} -> {}: No path\n", start, goal);
} catch (goal_visitor::found) {
std::list<Vertex> path;
for (auto cursor = goal;;) {
path.push_front(cursor);
auto previous = std::exchange(cursor, predecessors.at(cursor));
if (cursor == previous)
break;
}
fmt::print("{} -> {}: {}\n", start, goal, path);
}
As you can see everything is basically unchanged, except the distance_heuristic class has been replaced by the much simpler lambda:
auto heuristic = [&, gja = graph[goal].joint_angles](Vertex u) {
return bg::distance(graph[u].joint_angles, gja);
};
This effectively does the same as your manual heuristic, except potentially smarter - who knows :).
Possible outputs. Doing 1000 random searches took ~1.8s:
Parsed 50000 vertices, indexed: 50000 graph: 50000 0.161082s
Total edges added:150778 dups:99222 0.190395s
7489 -> 8408: [7489, 23635, 34645, 41337, 1725, 46184, 25161, 33297, 30471, 37500, 4073, 30763, 4488, 30949, 9505, 48543, 33639, 35640, 19525, 34765, 18439, 21830, 4170, 27552, 22621, 6327, 8277, 8082, 15932, 23390, 8408]
6968 -> 49906: [6968, 43210, 9331, 36641, 15088, 45635, 47530, 9136, 18177, 30781, 46243, 21125, 12868, 42416, 46187, 24824, 39841, 39095, 13494, 27104, 34973, 49906]
39242 -> 46236: [39242, 34365, 14041, 30310, 8757, 35459, 41035, 32883, 1552, 24120, 43646, 38812, 17835, 14082, 46568, 37492, 17564, 4934, 28288, 20393, 924, 14615, 15993, 39413, 10407, 46236]
--
31949 -> 38708: [31949, 16473, 18328, 20099, 22828, 42868, 46176, 22766, 49370, 17479, 636, 6173, 36367, 32040, 16961, 48438, 18883, 44611, 19468, 4095, 18156, 33083, 12925, 41017, 17514, 17765, 19710, 25790, 46668, 28202, 12010, 39520, 17796, 45443, 9474, 17370, 5071, 27279, 17083, 3503, 11401, 11209, 32403, 23265, 38708]
9895 -> 41286: [9895, 7793, 34802, 28190, 24889, 578, 49750, 20217, 41057, 2637, 24109, 4262, 38363, 11680, 7513, 39893, 21158, 15747, 33531, 11051, 7893, 31583, 45825, 18988, 38405, 13631, 31016, 45820, 9078, 37368, 28401, 14573, 9294, 6214, 28330, 22949, 10575, 41286]
42176 -> 37875: [42176, 12091, 19799, 41080, 47399, 30041, 41714, 10766, 8904, 41305, 4973, 21270, 18139, 29246, 34739, 35599, 11807, 36557, 48764, 9641, 3619, 11747, 34201, 33629, 20414, 24646, 43402, 36831, 7384, 29363, 24768, 33415, 41325, 17709, 32108, 42284, 28683, 5310, 1506, 14339, 27331, 14861, 7152, 37211, 22754, 7602, 48398, 27378, 39577, 37875]
Total search time: 1.79371s
real 0m2,209s
user 0m2,160s
sys 0m0,044s
Complete Benchmark
Live On Coliru
#include <boost/fusion/adapted/std_array.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/graph/random.hpp>
#include <chrono>
#include <fmt/ranges.h>
#include <fstream>
#include <random>
static auto now = &std::chrono::steady_clock::now;
using namespace std::chrono_literals;
using JointAngles = std::array<double, 3>;
struct VertexProperties {
JointAngles joint_angles{0, 0, 0};
};
struct EdgeProperties {
double weight = 0;
};
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS,
VertexProperties, EdgeProperties>;
using Vertex = Graph::vertex_descriptor;
template <typename F> size_t read_vertices(std::string_view input, F callback) {
using namespace boost::spirit::x3;
using boost::fusion::at_c;
Vertex n = 0;
auto action = [&](auto& ctx) {
auto& vv = _attr(ctx);
callback(JointAngles{at_c<0>(vv), at_c<1>(vv), at_c<2>(vv)});
n += 1;
};
static auto const line = (double_ >> ',' >> double_ >> ',' >> double_)[action];
parse(begin(input), end(input), skip(blank)[line % (eol | eoi) > (*eol >> eoi)]);
return n;
}
// visitor that terminates when we find the goal
struct goal_visitor : boost::default_astar_visitor {
struct found {}; // exception for termination
Vertex m_goal;
goal_visitor(Vertex g) : m_goal(g) {}
template <class Graph> void examine_vertex(Vertex u, Graph&) {
if (u == m_goal)
throw found{};
}
};
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/adapted/std_array.hpp>
#include <boost/geometry/index/adaptors/query.hpp>
#include <boost/geometry/index/rtree.hpp>
namespace bg = boost::geometry;
namespace bgi = bg::index;
using bgi::adaptors::queried;
BOOST_GEOMETRY_REGISTER_STD_ARRAY_CS(bg::cs::cartesian)
using Tree = bgi::rtree<std::pair<JointAngles, Vertex>, bgi::rstar<16>>;
int main() {
auto elapsed = [start = now()]() mutable {
auto n = now();
return (n - std::exchange(start, n)) / 1.0s;
};
// read and index vertices
Tree tree;
Graph graph;
std::ifstream ifs("input.txt", std::ios::binary);
std::string const input(std::istreambuf_iterator<char>(ifs), {});
graph.m_vertices.reserve(50'000);
auto const n = read_vertices(input, [&](JointAngles ja) {
tree.insert({ja, add_vertex(VertexProperties{ja}, graph)});
});
std::cout << "Parsed " << n << " vertices, indexed: " << tree.size()
<< " graph: " << num_vertices(graph) << " " << elapsed() << "s\n";
assert(n == tree.size());
assert(n == num_vertices(graph));
// connect 5-degree nearest vertices
size_t added = 0, dups =0;
for (auto& [vja, v] : tree) {
for (auto& [uja, u] : tree | queried(bgi::nearest(vja, 6))) {
if (v == u)
continue;
auto w = bg::distance(vja, uja);
auto [e, ok] = add_edge(v, u, EdgeProperties{w}, graph);
//std::cout << (ok ? "Added " : "Duplicate ") << e << " weight " << w << "\n";
(ok? added:dups)++;
}
}
std::cout << "Total edges added:" << added << " dups:" << dups << " " << elapsed() << "s\n";
// do A* search
std::vector<Vertex> predecessors(n);
std::vector<double> distances(n);
for (auto i = 0; i < 1'000; ++i) {
auto vidx = get(boost::vertex_index, graph); // redundant with vecS
auto pmap = make_iterator_property_map(predecessors.data(), vidx);
auto dmap = make_iterator_property_map(distances.data(), vidx);
auto weightmap = get(&EdgeProperties::weight, graph);
std::mt19937 gen(std::random_device{}());
Vertex start = random_vertex(graph, gen);
Vertex goal = random_vertex(graph, gen);
try {
// call astar named parameter interface
auto heuristic = [&, gja = graph[goal].joint_angles](Vertex u) {
return bg::distance(graph[u].joint_angles, gja);
};
astar_search( //
graph, start, heuristic,
boost::predecessor_map(pmap) //
.distance_map(dmap)
.weight_map(weightmap)
.visitor(goal_visitor{goal}));
fmt::print("{} -> {}: No path\n", start, goal);
} catch (goal_visitor::found) {
std::list<Vertex> path;
for (auto cursor = goal;;) {
path.push_front(cursor);
auto previous = std::exchange(cursor, predecessors.at(cursor));
if (cursor == previous)
break;
}
fmt::print("{} -> {}: {}\n", start, goal, path);
}
}
std::cout << "Total search time: " << elapsed() << "s\n";
}
On Coliru, takes a little longer:
Parsed 50000 vertices, indexed: 50000 graph: 50000 0.252916s
Total edges added:150778 dups:99222 0.38979s
43176 -> 2998: [43176, 8919, 27234, 38221, 8714, 2907, 45819, 32924, 33376, 14539, 9174, 19001, 30909, 3923, 36332, 4521, 43005, 31867, 7326, 46231, 20699, 24026, 44641, 21918, 43012, 37366, 2800, 14239, 21197, 26989, 38269, 16522, 25964, 18224, 47148, 21553, 19350, 37546, 41390, 1247, 2998]
19955 -> 30654: [19955, 18833, 24521, 9310, 29015, 5746, 46264, 7706, 4929, 11078, 41910, 30676, 26759, 16638, 3075, 23001, 9322, 38446, 20634, 1120, 30761, 47535, 15750, 10039, 34123, 42874, 22325, 24136, 30285, 34230, 23926, 9978, 4427, 23805, 10436, 41678, 46936, 37189, 30654]
45710 -> 21757: [45710, 45416, 1375, 16480, 21730, 22843, 15897, 33652, 12561, 46834, 23178, 44302, 21027, 15457, 38383, 14716, 26787, 20697, 41752, 42153, 44194, 21757]
--
16543 -> 43355: [16543, 44982, 27516, 6578, 27706, 39013, 35842, 33455, 30460, 22955, 579, 46537, 43224, 6811, 1651, 41054, 21637, 9496, 36577, 21896, 49329, 43355]
2856 -> 24431: [2856, 21766, 1449, 2525, 15156, 6325, 23773, 25733, 48449, 24269, 49865, 34213, 47119, 48167, 12609, 46284, 33395, 10107, 26726, 14078, 28431, 33884, 468, 39873, 42529, 32395, 49457, 44554, 2207, 47678, 4783, 14247, 39638, 8510, 9439, 20570, 18018, 34614, 37184, 17579, 49921, 8755, 44316, 24431]
17195 -> 21888: [17195, 38851, 28287, 18829, 14051, 28305, 32206, 11044, 6989, 30201, 49002, 19410, 6456, 47912, 35145, 9286, 17782, 10294, 14344, 49966, 49634, 5262, 12496, 45270, 20093, 11298, 7202, 15409, 41313, 35934, 14510, 17221, 23121, 49522, 38138, 45948, 43564, 7840, 4456, 32016, 16660, 5832, 7578, 380, 9925, 18908, 38131, 36929, 28073, 21888]
Total search time: 3.41871s
Related
BGL bundled edge properties containing vector [duplicate]
I have a boost graph with multiples weights for each edges (imagine one set of weights per hour of the day). Every one of those weights values is stored in a propretyEdge class : class propretyEdge { std::map<std::string,double> weights; // Date indexed } I created a graph with those properties, and then filled it with the right values. The problem is now that I want to launch the Dijkstra algorithm over a particular set of weight on the graph : for example a function that could be : void Dijkstra (string date, parameters ... ) That would use the weights[date] value for each Edge of the graph. I read over and over the documentation, and I couldn't have a clear picture of what I have to do. I surely need to write something like this, but I have no idea were to start : boost::dijkstra_shortest_paths ( (*graph_m), vertex_origin_num_l, // weight_map (get (edge_weight, (*graph_m))) // predecessor_map(boost::make_iterator_property_map(predecessors.begin(), get(boost::vertex_index, (*graph_m)))). // distance_map(boost::make_iterator_property_map(distances.begin (), get(vertex_index,(*graph_m) ))) predecessor_map(predecessorMap). distance_map(distanceMap) ); Thank you for your help. Edit Thanks to the wonderful Answer of Sehe, I was able to do exactly what I wanted on MacOS and on Ubuntu. But when we tried to compile this piece of code on Visual Studio 2012, it appeared that VS wasn't very good at understanding pointer function of boost. So we modified the part of Sehe : auto dated_weight_f = [&](Graph::edge_descriptor ed) { return g[ed].weights.at(date); }; auto dated_weight_map = make_function_property_map<Graph::edge_descriptor, double>(dated_weight_f); by : class dated_weight_f { public: dated_weight_f(Graph* graph_p,std::string date_p){ graph_m=graph_p; date_m=date_p; } typedef double result_type; result_type operator()(Edge edge_p) const{ return (*graph_m)[edge_p].weights.at(date_m); } private: Graph* graph_m; std::string date_m; }; const auto dated_weight_map = make_function_property_map<Edge>(dated_weight_f(graph_m,date_l)); Which had the advantage of not using a pointer function.
Since it's apparently not immediately clear that this question is answered in the other answer, I'll explain. All you really need is a custom weight_map parameter that is "stateful" and can select a certain value for a given date. You can make this as complicated as you wish ¹, so you could even interpolate/extrapolate a weight given an unknown date ², but let's for the purpose of this demonstration keep it simple. Let's define the graph type (roughly) as above: struct propretyEdge { std::map<std::string, double> weights; // Date indexed }; using Graph = adjacency_list<vecS, vecS, directedS, no_property, propretyEdge>; Now, let's generate a random graph, with random weights for 3 different dates: int main() { Graph g; std::mt19937 prng { std::random_device{}() }; generate_random_graph(g, 8, 12, prng); uniform_real<double> weight_dist(10,42); for (auto e : make_iterator_range(edges(g))) for (auto&& date : { "2014-01-01", "2014-02-01", "2014-03-01" }) g[e].weights[date] = weight_dist(prng); And, jumping to the goal: for (std::string const& date : { "2014-01-01", "2014-02-01", "2014-03-01" }) { Dijkstra(date, g, 0); } } Now how do you implement Dijkstra(...)? Gleaning from the documentation sample, you'd do something like void Dijkstra(std::string const& date, Graph const& g, int vertex_origin_num_l = 0) { // magic postponed ... std::vector<Graph::vertex_descriptor> p(num_vertices(g)); std::vector<double> d(num_vertices(g)); std::vector<default_color_type> color_map(num_vertices(g)); boost::typed_identity_property_map<Graph::vertex_descriptor> vid; // T* property maps were deprecated dijkstra_shortest_paths(g, vertex_origin_num_l, weight_map(dated_weight_map). predecessor_map(make_iterator_property_map(p.data(), vid)). distance_map(make_iterator_property_map(d.data(), vid)). color_map(make_iterator_property_map(color_map.data(), vid)) ); Now the only unclear bit here should be dated_weight_map. Enter Boost Property Maps As I showed in the linked Is it possible to have several edge weight property maps for one graph BOOST?, you can have all kinds of property maps ³, including invocation of user-defined functions. This is the missing piece: auto dated_weight_f = [&](Graph::edge_descriptor ed) { return g[ed].weights.at(date); }; auto dated_weight_map = make_function_property_map<Graph::edge_descriptor, double>(dated_weight_f); Voilà: done I hope that by now, the correspondence in the question as well as the answer of the linked question is clear. All that's left to do is post the full live sample and the outcome in a pretty picture: Live On Coliru #include <boost/property_map/property_map.hpp> #include <boost/property_map/function_property_map.hpp> #include <boost/property_map/property_map_iterator.hpp> #include <random> #include <boost/graph/random.hpp> #include <boost/graph/adjacency_list.hpp> #include <boost/graph/dijkstra_shortest_paths.hpp> #include <fstream> using namespace boost; struct propretyEdge { std::map<std::string, double> weights; // Date indexed }; using Graph = adjacency_list<vecS, vecS, directedS, no_property, propretyEdge>; void Dijkstra(std::string const& date, Graph const& g, int vertex_origin_num_l = 0) { auto dated_weight_f = [&](Graph::edge_descriptor ed) { return g[ed].weights.at(date); }; auto dated_weight_map = make_function_property_map<Graph::edge_descriptor, double>(dated_weight_f); std::vector<Graph::vertex_descriptor> p(num_vertices(g)); std::vector<double> d(num_vertices(g)); std::vector<default_color_type> color_map(num_vertices(g)); boost::typed_identity_property_map<Graph::vertex_descriptor> vid; // T* property maps were deprecated dijkstra_shortest_paths(g, vertex_origin_num_l, weight_map(dated_weight_map). predecessor_map(make_iterator_property_map(p.data(), vid)). distance_map(make_iterator_property_map(d.data(), vid)). color_map(make_iterator_property_map(color_map.data(), vid)) ); std::cout << "distances and parents for '" + date + "':" << std::endl; for (auto vd : make_iterator_range(vertices(g))) { std::cout << "distance(" << vd << ") = " << d[vd] << ", "; std::cout << "parent(" << vd << ") = " << p[vd] << std::endl; } std::cout << std::endl; std::ofstream dot_file("dijkstra-eg-" + date + ".dot"); dot_file << "digraph D {\n" " rankdir=LR\n" " size=\"6,4\"\n" " ratio=\"fill\"\n" " graph[label=\"shortest path on " + date + "\"];\n" " edge[style=\"bold\"]\n" " node[shape=\"circle\"]\n"; for (auto ed : make_iterator_range(edges(g))) { auto u = source(ed, g), v = target(ed, g); dot_file << u << " -> " << v << "[label=\"" << get(dated_weight_map, ed) << "\"" << (p[v] == u?", color=\"black\"" : ", color=\"grey\"") << "]"; } dot_file << "}"; } int main() { Graph g; std::mt19937 prng { std::random_device{}() }; generate_random_graph(g, 8, 12, prng); uniform_real<double> weight_dist(10,42); for (auto e : make_iterator_range(edges(g))) for (auto&& date : { "2014-01-01", "2014-02-01", "2014-03-01" }) g[e].weights[date] = weight_dist(prng); for (std::string const& date : { "2014-01-01", "2014-02-01", "2014-03-01" }) { Dijkstra(date, g, 0); } } Output, e.g. ¹ As long as you keep the invariants required by the algorithm you're invoking. In particular, you must return the same weight consistently during the execution, given the same edge. Also, some algorithms don't support negative weight etc. ² I'd highly suggest using a Boost ICL interval_map in such a case but I digress ³ see also map set/get requests into C++ class/structure changes
BGL: Get initial direction of edge in undirected graph
In an undirected BGL graph: Can I get the information whether an edge (u,v) == (v,u) was initially added as (u,v) or as (v,u)? Background: I created a graph using Pythons graph-tool library which internally uses Boost Graph Library (BGL). Each edge has a "directed attribute" referring to source and target of the edge: (source_range, target_range). I want to perform an undirected Depth-First search to find all paths between two nodes. I'm using graph-tools get_all_paths() as a basis. I altered the underlying BGL implementation in a way that traversal of the graph depends on the "directed attribute". I got it working for the directed case. However when I switch the graph to undirected I have the problem that I don't know the initial direction of an edge. Thus I don't know the ordering of the edge attribute: (source_range, target_range) vs (target_range, source_range) Here is my DFS code with the mentioned stop criterion (// Check overlap part): template <class Graph, class Yield, class VMap, class EMap> void get_all_paths(size_t s, size_t t, size_t cutoff, VMap visited, EMap startend, Yield& yield, Graph& g) { typedef typename graph_traits<Graph>::out_edge_iterator eiter_t; typedef std::pair<eiter_t, eiter_t> item_t; visited[s] = true; // set visited true for source node // could also use refrences to startend property map here. meh... uint8_t t_start_e1, t_end_e1, q_start_e1, q_end_e1; uint8_t q_start_e2, q_end_e2, t_start_e2, t_end_e2; int32_t startend_e1; int32_t startend_e2; typedef typename property_map<Graph, vertex_index_t>::type IndexMap; IndexMap index = get(vertex_index, g); vector<size_t> vs = {s}; // vector of indexes vector<item_t> stack = {out_edges(s, g)}; // vector of edge_iterator pairs while (!stack.empty()) { std::cout << "Stack before check overlap: "; for (uint8_t i=0; i<stack.size(); i++) { std::cout << " (" << source(*stack[i].first, g) << "," << target(*stack[i].first, g) << ") "; } std::cout << "\n"; auto& pos = stack.back(); // last element in eiter vector // End of path because of self loop or cutoff is reached if (pos.first == pos.second || stack.size() > cutoff) { visited[vs.back()] = false; // revoke visited flag for last node vs.pop_back(); stack.pop_back(); if (!stack.empty()) ++stack.back().first; // increment first iterator continue; } // Check overlap if (stack.size() > 1) { auto& pos_prev = *(stack.rbegin() + 1); // second last eiter startend_e1 = startend[*pos_prev.first]; startend_e2 = startend[*pos.first]; std::cout << "Checking Edges: (" << source(*pos_prev.first, g) << "," << target(*pos_prev.first, g) << ")"; std::cout << " (" << source(*pos.first, g) << "," << target(*pos.first, g) << "):"; // take apart 2x int32_t to 8x int8_t (memory optimization) // Undirected case:If the edge was added // as (u,v) and (v,u) was detected // I need to swap q(uery) and t(arget) values here. // --> How can I detect if (u,v) was initially added as (u,v) // or (v, u) q_start_e1 = startend_e1 & 0xFF; q_end_e1 = (startend_e1 >> 8) & 0xFF; t_start_e1 = (startend_e1 >> 16) & 0xFF; t_end_e1 = (startend_e1 >> 24) & 0xFF; q_start_e2 = startend_e2 & 0xFF; q_end_e2 = (startend_e2 >> 8) & 0xFF; t_start_e2 = (startend_e2 >> 16) & 0xFF; t_end_e2 = (startend_e2 >> 24) & 0xFF; if ((min(t_end_e1, q_end_e2) - max(t_start_e1, q_start_e2)) < 1) { std::cout << "Failed\n"; ++pos.first; std::cout << "Stack after check overlap: "; for (uint8_t i=0; i<stack.size(); i++) { std::cout << "(" << source(*stack[i].first, g) << "," << target(*stack[i].first, g) << ") "; } std::cout << "\n"; continue; } std::cout << "Passed\n"; } auto v = target(*pos.first, g); // get target vertex // reached target node if (v == t) { vector<size_t> path = {s}; // path vector for (auto& ei : stack) path.push_back(target(*ei.first, g)); yield(wrap_vector_owned<size_t>(path)); // yield path ++pos.first; // increment eiter } else { //check if node was visited if (!visited[v]) //not visited { visited[v] = true; vs.push_back(v); stack.push_back(out_edges(v, g)); } else // visited { ++pos.first; } } } }; Thank you for your help! Update: I came up with the following workaround for my problem. I have an edge property (some_val_ref_u, some_val_ref_v) of the edge (u,v). In an undirected graph the edge (v,u) will still have the edge property (some_val_ref_u, some_val_ref_v). Thus I would assign some_val_ref_u to v and some_val_ref_v to u, which is not correct. I have to take the order into account when dealing with a "reverse edge". The solution I came up with is to set the order dynamically when creating the graph depending on the edge index of v and u. if edge_index[v] < edge_index[u]: g.ep.myattr[g.edge(v,u)] = (some_val_ref_v, some_val_ref_u) else: g.ep.myattr[g.edge(v,u)] = (some_val_ref_u, some_val_ref_v) So the order of the edge property tuple depends on which edge index is smaller. Consequently, when traversing the graph, I can decide the order of the edge attribute by comparing the vertex indices. This does not directly answer my question but hopefully will be a workaround for my problem.
You can simply iterate: for (auto ed : boost::make_iterator_range(edges(g))) { std::cout << "Added as " << ed << " (so " << source(ed, g) << "->" << target(ed, g) << ")\n"; } This is due to the adjacency lists storing lists of adjacencies per node. For a directed graph this loop would effectively be equivalent to doing: for (auto from : boost::make_iterator_range(vertices(g))) { for (auto to : boost::make_iterator_range(adjacent_vertices(from, g))) std::cout << "Added as " << from << "->" << to << "\n"; } However, for undirected graphs this would list all non-self edges duplicate. Live On Coliru #include <boost/graph/adjacency_list.hpp> #include <boost/graph/graph_utility.hpp> #include <iostream> void testcase(int from, int to) { using namespace boost; adjacency_list<vecS, vecS, undirectedS> g(2); add_edge(from, to, g); for (auto ed : boost::make_iterator_range(edges(g))) { std::cout << "Added as " << ed << " (so " << source(ed, g) << "->" << target(ed, g) << ")\n"; } } int main() { testcase(0, 1); testcase(1, 0); } Prints Added as (0,1) (so 0->1) Added as (1,0) (so 1->0)
sehe's answer is incorrect - there is no way of obtaining original source and target vertices from any edge descriptor, as shown below: #include <iostream> #include <boost/graph/adjacency_list.hpp> #include <boost/graph/graph_utility.hpp> int main() { using namespace boost; typedef adjacency_list<vecS, vecS, undirectedS> Graph; Graph g(2); add_edge(0, 1, g); for (auto ed : make_iterator_range(edges(g))) { std::cout << "Added as " << ed << " (so " << source(ed, g) << "->" << target(ed, g) << ")\n"; } //edge (1,0) exists since the graph is undirected, //yet source and target vertices are not the way they were originally specified Graph::edge_descriptor ed = edge(1, 0, g).first; std::cout << ed << " (so " << source(ed, g) << "->" << target(ed, g) << ')'; } One solution, as already mentioned, is to iterate through all the edges. The other one, more efficient, is to seek for vertex v in out edges of vertex u (or the other way around): #include <iostream> #include <cassert> #include <boost/graph/adjacency_list.hpp> #include <boost/graph/graph_utility.hpp> int main() { using namespace boost; typedef adjacency_list<vecS, vecS, undirectedS> Graph; Graph g(2); add_edge(0, 1, g); Graph::EdgeContainer::const_iterator edge1 = std::find(g.out_edge_list(0).begin(), g.out_edge_list(0).end(), Graph::StoredEdge(1))->get_iter(); Graph::EdgeContainer::const_iterator edge2 = std::find(g.out_edge_list(1).begin(), g.out_edge_list(1).end(), Graph::StoredEdge(0))->get_iter(); assert(edge1 == edge2); std::cout << source(*edge1, g) << "->" << target(*edge1, g); }
How to use get() boost graph
I am trying to implement my own A star algorithm. However, I've only come up with a way to get my edge weight values through iterators. Since Iterators access the property map in order, I cannot prioritize edges and need to do a full loop for each vertex to find the neighbors, this would make the algorithm really slow. Is there any way to use get(), put()... without iterators, selecting the vertices of which I want to check the edges? This is my program at the moment: struct Point {//struct point with vertex properties int x, y; int parentx, parenty; double g; double h; friend std::ostream& operator<<(std::ostream& os, Point p) { return os << "[" << p.x << "," << p.y << "]"; } }; int main() { //declarations typedef property < edge_weight_t, double >Weight; using std::vector;//? using Graph = adjacency_list<setS, vecS, undirectedS, Point, Weight>;//graph includes our created point struct property<edge_weight_t using vertex_descriptor = Graph::vertex_descriptor; Graph lattuce; //lattuce graph is created with weighted edges value 1 or 1,41 if diagonal. The functions used on a loop are: //add_edge(nodes[p.x][p.y],nodes[neighbour.x][neighbour.y], Weight(1.0), lattuce); //add_edge(nodes[p.x][p.y],nodes[neighbour.x][neighbour.y], Weight(1.4), lattuce); typedef Graph::edge_iterator EdgeIterator; std::pair<EdgeIterator, EdgeIterator> edges = boost::edges(lattuce); typedef boost::property_map<Graph, boost::edge_weight_t>::type WeightMap; WeightMap weights = boost::get(boost::edge_weight_t(), lattuce); //cout<<get(weights,(1,2)); EdgeIterator edge; for (edge = edges.first; edge != edges.second; ++edge) { std::cout << boost::get(weights, *edge) <<" "<< *edge<< " "; if (source(*edge, lattuce) == origin || target(*edge, lattuce)==origin ){ get(weights, *edge); cout<<"Edge related to the origin "; } if (source(*edge, lattuce) == end_vertex || target(*edge, lattuce)==end_vertex ){ cout<<"Edge related to the end_vertex "; } if (get(weights, *edge)==1.41){ cout<<" Diagonal"<<endl; } else if (get(weights, *edge)==1){ cout<<" Unitary"<<endl; } else if (get(weights, *edge)==99999999.0){ cout<<"Infinite"<<endl; } } } Also, would like to be able to modify the values of f, h and parent within a Point struct. Any help or guidance will be greatly appreciated.
Answering your questions You can use vertex descriptors instead of the iterators. In your case, because you are using bundled properties, you can use the operator[descriptor] shorthand on the graph. You can use this to alter the bundled properties as long as your graph is not const. Code It's not actually clear to me what the code in your question was supposed to illustrate. I'll simply reply with a similarly dissociated piece of code that built from there and shows you a few things that might be enlightening :) Live On Coliru #include <boost/graph/adjacency_list.hpp> #include <boost/graph/astar_search.hpp> #include <boost/graph/random.hpp> #include <iostream> #include <random> static std::mt19937 s_rng { std::random_device{}() }; static std::uniform_int_distribution<> s_coord(-10,10); static std::uniform_real_distribution<double> s_double(-1.,1.); static int gen_coord() { return s_coord(s_rng); } static int gen_gh () { return s_double(s_rng); } struct Point { // struct point with vertex properties int x = gen_coord(), y = gen_coord(); int parentx = gen_coord(), parenty = gen_coord(); double g = gen_gh(); double h = gen_gh(); friend std::ostream &operator<<(std::ostream &os, Point p) { return os << "[" << p.x << "," << p.y << "]"; } }; // declarations typedef boost::property<boost::edge_weight_t, double> Weight; using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS, Point, Weight>; using vertex_descriptor = Graph::vertex_descriptor; int main() { Graph lattuce; using namespace boost; generate_random_graph(lattuce, 10, 20, s_rng); vertex_descriptor origin = vertex(0, lattuce); vertex_descriptor end_vertex = vertex(9, lattuce); typedef boost::property_map<Graph, boost::edge_weight_t>::type WeightMap; WeightMap weights = boost::get(boost::edge_weight_t(), lattuce); for (Graph::edge_descriptor edge : make_iterator_range(edges(lattuce))) { std::cout << boost::get(weights, edge) << " " << edge << " "; vertex_descriptor src = source(edge, lattuce); vertex_descriptor trg = target(edge, lattuce); Point& src_bundle = lattuce[src]; Point& trg_bundle = lattuce[trg]; std::cout << "Edge: " << src_bundle << " -- " << trg_bundle << "\n"; src_bundle.g *= 1.1; src_bundle.h *= -1.1; //double weight = get(weights, edge); } (void) origin; // unused (void) end_vertex; // unused } Printing, for example: 0 (7,5) Edge: [-5,-5] -- [-9,-6] 0 (9,2) Edge: [3,2] -- [-3,3] 0 (3,4) Edge: [-4,-10] -- [-10,-5] 0 (1,6) Edge: [3,4] -- [-7,7] 0 (9,4) Edge: [3,2] -- [-10,-5] 0 (3,8) Edge: [-4,-10] -- [-6,0] 0 (1,9) Edge: [3,4] -- [3,2] 0 (5,9) Edge: [-9,-6] -- [3,2] 0 (6,9) Edge: [-7,7] -- [3,2] 0 (0,1) Edge: [-9,8] -- [3,4] 0 (1,4) Edge: [3,4] -- [-10,-5] 0 (0,2) Edge: [-9,8] -- [-3,3] 0 (9,3) Edge: [3,2] -- [-4,-10] 0 (2,4) Edge: [-3,3] -- [-10,-5] 0 (7,6) Edge: [-5,-5] -- [-7,7] 0 (1,2) Edge: [3,4] -- [-3,3] 0 (0,9) Edge: [-9,8] -- [3,2] 0 (4,7) Edge: [-10,-5] -- [-5,-5] 0 (1,5) Edge: [3,4] -- [-9,-6] 0 (0,7) Edge: [-9,8] -- [-5,-5]
Longest Path in Boost Graph
Sorry if this is a very basic questions for some of you but I'm new to C++ (let alone Boost Graph Library) and couldn't figure out this problem. So far I've been able to formulate/gather code to create a graph using the code below. Now I'm trying to figure out the code to find the longest path in this graph. Can someone please help with what would the code be? I was having trouble trying to figure out if/how to traverse through each node and/or edge when trying to find the path? I have to try to return all the nodes and edges in the longest path. Any help will be greatly appreciated. P.S. does anyone know if C++ has organized documentation like Javadoc?? #include <boost/graph/dag_shortest_paths.hpp> #include <boost/graph/adjacency_list.hpp> #include <windows.h> #include <iostream> int main() { using namespace boost; typedef adjacency_list<vecS, vecS, directedS, property<vertex_distance_t, double>, property<edge_weight_t, double> > graph_t; graph_t g(6); enum verts { stationA, stationB, stationC, stationD, stationE, stationF }; char name[] = "rstuvx"; add_edge(stationA, stationB, 5000.23, g); add_edge(stationA, stationC, 3001, g); add_edge(stationA, stationD, 2098.67, g); add_edge(stationA, stationE, 3298.84, g); add_edge(stationB, stationF, 2145, g); add_edge(stationC, stationF, 4290, g); add_edge(stationD, stationF, 2672.78, g); add_edge(stationE, stationF, 11143.876, g); add_edge(stationA, stationF, 1, g); //Display all the vertices typedef property_map<graph_t, vertex_index_t>::type IndexMap; IndexMap index = get(vertex_index, g); std::cout << "vertices(g) = "; typedef graph_traits<graph_t>::vertex_iterator vertex_iter; std::pair<vertex_iter, vertex_iter> vp; for (vp = vertices(g); vp.first != vp.second; ++vp.first) std::cout << index[*vp.first] << " "; std::cout << std::endl; // ... // Display all the edges // ... std::cout << "edges(g) = " << std::endl; graph_traits<graph_t>::edge_iterator ei, ei_end; for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) std::cout << "(" << index[source(*ei, g)] << "," << index[target(*ei, g)] << ") \n"; std::cout << std::endl; // ...
I think you should check the example in your boost distribution. Online : http://www.boost.org/doc/libs/1_38_0/libs/graph/example/dijkstra-example.cpp To make it find the longest path you need to simply inverse the weight (W), either use a Constant - W, or 1/W. If the constant is 0, then it means it's a negation (-W).
I agree that one has to be careful about sign reversal. Firstly, most shortest path algorithms are only for positive edge graphs. You do have some options (e.g Bellman-Ford algorithm) that generalize to graphs with negative weight, they are not guaranteed to return the optimal answer if there are negative cycles in the graph.
Constructing the contour of a polygon (in particular a triangulation)
How would I go about constructing the contour of 2d polygon which is formed of only triangles and it can have holes and the external contour can be concave/convex and the holes can also be concave/convex. From what I'm reading over here it seems that It's exactly the inverse of the triangulation problem. Do you know any articles treating this type of problem? Are octrees/quadtrees relevant to this?
I guess that you have data in the form of sets of three points, which constitute a "filled" triangle, that these triangles are adjoined along edges, and that all vertices that will be corners of the complete shape are also vertices of all the triangles that touch this point. You would then just have to find all edges that are not doubled, i.e. do not belong to two adjoined triangles.
I think you can solve your problem by creating a topological data structure to represent your set of triangles, and then using that structure to iterate in order over the triangle edges that lie on the boundary. For example: you can create a halfedge data structure. Assuming you insert halfedges even on the boundary (correctly), iterating over the boundary contour is as simple as locating one halfedge on the boundary and then iterating over it's "next" pointer until you get back to the halfedge you started from. Similarly to halfedges, you can use other topological structures like winged-edge, etc., but the concept is the same.
Here is an implementation operating on a triangle-mesh, finding and connecting all non-doubled edges as explained also in this answer. #include <list> #include <map> #include <set> #include <vector> #include <iostream> typedef int Vertex; class Triangle { public: const Vertex& operator [] (size_t i) const { return p[i]; } Vertex p[3]; }; std::list<std::list<Vertex>> find_contours(const std::vector<Triangle>& triangles) { std::set<std::pair<Vertex, Vertex>> edges; std::map<Vertex, Vertex> neighbors; for(const auto& t : triangles) { edges.insert(std::make_pair(t[0], t[1])); edges.insert(std::make_pair(t[1], t[2])); edges.insert(std::make_pair(t[2], t[0])); } for(const auto& t : triangles) { edges.erase(std::make_pair(t[1], t[0])); edges.erase(std::make_pair(t[2], t[1])); edges.erase(std::make_pair(t[0], t[2])); } for(const auto& t : triangles) { if (edges.find(std::make_pair(t[0], t[1])) != edges.end()) { neighbors[t[0]] = t[1]; } if (edges.find(std::make_pair(t[1], t[2])) != edges.end()) { neighbors[t[1]] = t[2]; } if (edges.find(std::make_pair(t[2], t[0])) != edges.end()) { neighbors[t[2]] = t[0]; } } std::list<std::list<Vertex>> result; while(!neighbors.empty()) { std::list<Vertex> contour; auto v0 = neighbors.begin()->first; auto v = v0; while(neighbors.find(v) != neighbors.end()) { contour.push_back(v); auto old_v = v; v = neighbors.at(v); neighbors.erase(old_v); } if (v != v0) { throw std::runtime_error("Contour is not closed"); } neighbors.erase(v); result.push_back(contour); } return result; } int main() { int v00 = 0; int v10 = 1; int v01 = 2; int v11 = 3; std::vector<Triangle> v{ {v00, v10, v11}, {v00, v11, v01}}; for(const auto& c : find_contours(v)) { for(const auto& v : c) { std::cerr << v << " | "; } std::cerr << std::endl; } std::cerr << std::endl; return 0; }