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
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);
}