Boost A* throwing segmentation fault - boost
this is the continuation of another question I asked regarding boost graphs. (GraphML in Boost).
After successfully reading the graph, I am trying to apply boost A* on some random start and goal nodes but its throwing segmentation fault.
Following are the details of my graph.
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS, VertexProperties, EdgeProperties>;
struct VertexProperties {
std::vector<double> joint_angles;
VertexProperties() : joint_angles(3){}
};
struct EdgeProperties {
double weight;
};
I used A* cities file from Boost as reference (A* Cities) to code my distance heuristic and astar_goal_visitor.
struct found_goal {}; // exception for termination
// visitor that terminates when we find the goal
template <typename Vertex>
class astar_goal_visitor : public boost::default_astar_visitor
{
public:
astar_goal_visitor(Vertex goal) : m_goal(goal) {}
template <class Graph>
void examine_vertex(Vertex u, Graph& g) {
if(u == m_goal)
throw found_goal();
}
private:
Vertex m_goal;
};
// euclidean distance heuristic
template <class Graph>
class distance_heuristic : public boost::astar_heuristic<typename Graph::Graph, double>
{
public:
typedef typename boost::graph_traits<typename Graph::Graph>::vertex_descriptor Vertex;
distance_heuristic(Vertex goal, Graph &graph)
: m_goal(goal), m_graph(graph) {}
double operator()(Vertex u)
{
double dx = m_graph.getGraph()[m_goal].joint_angles[0] - m_graph.getGraph()[u].joint_angles[0];
double dy = m_graph.getGraph()[m_goal].joint_angles[1] - m_graph.getGraph()[u].joint_angles[1];
double dz = m_graph.getGraph()[m_goal].joint_angles[2] - m_graph.getGraph()[u].joint_angles[2];
return ::sqrt(dx * dx + dy * dy + dz * dz);
}
private:
Graph m_graph;
Vertex m_goal;
};
As for astar_search parameters, the predecessor map is defined as below.
typedef boost::property_map < Graph, boost::vertex_index_t >::type IndexMap;
typedef boost::iterator_property_map < Vertex*, IndexMap, Vertex, Vertex& > PredecessorMap;
BoostGraph::PredecessorMap BoostGraph::getPredecessorMap(){
IndexMap indexMap = boost::get(boost::vertex_index, graph);
std::vector<Vertex> p(boost::num_vertices(graph));
PredecessorMap predecessorMap(&p[0], indexMap);
return predecessorMap;
}
The final code for search is
std::vector<double> d(boost::num_vertices(graph.getGraph()));
std::mt19937 gen(time(0));
BoostGraph::Vertex start = boost::random_vertex(graph.getGraph(), gen);
BoostGraph::Vertex goal = boost::random_vertex(graph.getGraph(), gen);
auto weightmap = boost::get(&EdgeProperties::weight, graph.getGraph());
try {
// call astar named parameter interface
boost::astar_search
(graph.getGraph(), start,
distance_heuristic<BoostGraph>
(goal, graph),
boost::predecessor_map(graph.getPredecessorMap()).distance_map(boost::make_iterator_property_map(d.begin(), boost::get(boost::vertex_index, graph.getGraph()))).
weight_map(weightmap).
visitor(astar_goal_visitor<BoostGraph::Vertex>(goal)));
} catch(found_goal fg) { // found a path to the goal
std::list<BoostGraph::Vertex> shortest_path;
for(BoostGraph::Vertex v = goal;; v = p[v]) {
shortest_path.push_front(v);
if(p[v] == v)
break;
}
}
The getGraph function of Class BoostGraph is defined below where graph is the protected member of class BoostGraph.
protected:
Graph graph;
const BoostGraph::Graph& BoostGraph::getGraph() const{
return graph;
}
The segmentation fault is coming in stl_tree.h and I have no idea what has gone wrong. Any help would be appreciated. Thanks
Your heuristic holds a copy of the graph. You're indexing using vertex descriptors belonging to different graphs.
Your predecessor map is a local variable (the vector), and the map is a dangling reference to it after getPredecessorMap returns. Just make the vector a member, and then getPredecessorMap can be eliminated, because it doesn't really add much.
Also, you're indexing into joint_angles without bounds checking. Prefer .at(n) over [n] if you want to be safer. In fact, consider using std::array<double, 3> instead of std::vector<double>.
All in all I get the impression that you've been trying to hide complexity in a class and member functions, however it leads to the code becoming fragmented and inviting lots of unnecessary lifetime issues.
There are also parts of the code you do not show. They likely hold more problems (e.g. getGraph() is crucial).
Here's my simplified take:
Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/graph/random.hpp>
using JointAngles = std::vector<double>;
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;
// 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{};
}
};
// euclidean distance heuristic
struct distance_heuristic : boost::astar_heuristic<Graph, double> {
distance_heuristic(Vertex goal, Graph& graph)
: m_graph(graph)
, m_goal(graph[goal].joint_angles) {}
double operator()(Vertex u) const {
auto& c = m_graph[u].joint_angles;
auto //
dx = m_goal.at(0) - c.at(0), //
dy = m_goal.at(1) - c.at(1), //
dz = m_goal.at(2) - c.at(2);
using std::sqrt; // allow ADL, good practice
return sqrt(dx * dx + dy * dy + dz * dz);
}
private:
Graph& m_graph; // reference!
JointAngles m_goal;
};
#include <random>
#include <fmt/ranges.h>
int main() {
Graph graph(4);
graph[0] = VertexProperties{{0, 0, 0}};
graph[1] = VertexProperties{{1, 1, 1}};
graph[2] = VertexProperties{{2, 2, 2}};
add_edge(0, 1, graph);
add_edge(1, 2, graph);
std::vector<Vertex> predecessors(num_vertices(graph));
std::vector<double> distances(num_vertices(graph));
auto index = get(boost::vertex_index, graph);
auto pmap = make_safe_iterator_property_map(predecessors.begin(), predecessors.size(), index);
auto dmap = make_safe_iterator_property_map(distances.begin(), distances.size(), index);
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
astar_search( //
graph, start, distance_heuristic{goal, graph},
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);
}
}
Which prints e.g.
2 -> 1: [2, 1]
Encapsulation?
If you want to encapsulate, do it along the functional boundaries, instead of artificial boundaries that gave you the lifetime headaches you didn't need. If performance is no concern, you can reduce code with a facility like vector_property_map. For example:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/astar_search.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/random.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/property_map/vector_property_map.hpp>
#include <fmt/ranges.h>
#include <random>
class BoostGraph {
private:
using JointAngles = std::vector<double>;
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;
public:
BoostGraph() : m_graph(4) {
// TODO read graph
m_graph[0] = VertexProperties{{0, 0, 0}};
m_graph[1] = VertexProperties{{1, 1, 1}};
m_graph[2] = VertexProperties{{2, 2, 2}};
add_edge(0, 1, m_graph);
add_edge(1, 2, m_graph);
}
friend std::ostream& operator<<(std::ostream& os, BoostGraph const& bg) {
auto name = boost::make_transform_value_property_map(
[ja = get(&VertexProperties::joint_angles, bg.m_graph)](Vertex v) {
return fmt::format("Vertex #{} ({})", v, ja[v]);
},
get(boost::vertex_index, bg.m_graph));
print_graph(bg.m_graph, name, os);
return os;
}
Vertex random_vertex() { return boost::random_vertex(m_graph, m_prng); }
std::list<Vertex> find_path(Vertex start, Vertex goal) const {
std::list<Vertex> path;
auto pmap = make_vector_property_map<Vertex>(get(boost::vertex_index, m_graph));
try {
astar_search( //
m_graph, start, distance_heuristic{goal, m_graph},
boost::predecessor_map(pmap) //
.weight_map(get(&EdgeProperties::weight, m_graph))
.visitor(finder{goal}));
} catch (finder::found) {
for (auto cursor = goal;;) {
path.push_front(cursor);
auto previous = std::exchange(cursor, pmap[cursor]);
if (cursor == previous)
break;
}
}
return path;
}
private:
// visitor that terminates when we find the goal
struct finder : boost::default_astar_visitor {
struct found {}; // exception for termination
Vertex m_goal;
finder(Vertex g) : m_goal(g) {}
void examine_vertex(Vertex u, Graph const&) const {
if (u == m_goal)
throw found{};
}
};
// euclidean distance heuristic
struct distance_heuristic : boost::astar_heuristic<Graph, double> {
distance_heuristic(Vertex goal, Graph const& graph)
: m_graph(graph)
, m_goal(graph[goal].joint_angles) {}
double operator()(Vertex u) const {
auto& c = m_graph[u].joint_angles;
auto //
dx = m_goal.at(0) - c.at(0), //
dy = m_goal.at(1) - c.at(1), //
dz = m_goal.at(2) - c.at(2);
using std::sqrt; // allow ADL, good practice
return sqrt(dx * dx + dy * dy + dz * dz);
}
private:
Graph const& m_graph; // reference!
JointAngles m_goal;
};
Graph m_graph;
std::mt19937 m_prng{std::random_device{}()};
};
int main() {
BoostGraph bg;
std::cout << "Graph: " << bg << "\n";
for (auto i = 0; i < 10; ++i) {
auto start = bg.random_vertex(), goal = bg.random_vertex();
auto path = bg.find_path(start, goal);
fmt::print("{} -> {}: {}\n", start, goal, path);
}
}
Printing e.g.
Graph: Vertex #0 ([0, 0, 0]) <--> Vertex #1 ([1, 1, 1])
Vertex #1 ([1, 1, 1]) <--> Vertex #0 ([0, 0, 0]) Vertex #2 ([2, 2, 2])
Vertex #2 ([2, 2, 2]) <--> Vertex #1 ([1, 1, 1])
Vertex #3 ([0, 0, 0]) <-->
2 -> 2: [2]
1 -> 0: [1, 0]
0 -> 1: [0, 1]
2 -> 0: [2, 1, 0]
3 -> 0: []
0 -> 3: []
0 -> 3: []
1 -> 0: [1, 0]
1 -> 0: [1, 0]
3 -> 1: []
Related
Create Edges in Boost Graph using Multi Threading
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
Finding all non-comparable nodes in DAG
I am interested in finding sets of vertices that are not ordered in a directed acyclic graph (in the sense of a topological order). That is, for example: two vertices in non-connected subgraphs, or the pairs (B,C), (B,D) in cases such as : The naive possibility I thought of was to enumerate all the topological sorts (in this case [ A, B, C, D ] and [ A, C, D, B ] & find all pairs whose order ends up being different in at least two sorts, but this would be pretty expensive computationally. Are there other, faster possibilities for what I want to achieve ? I am using boost.graph.
Basically what you want is the pair of nodes (u,v) such that there is no path from u to v, and no path from v to u. You can find for each node, all nodes that are reachable from that node using DFS. Total Complexity O(n(n+m)). Now all you have to do is for each pair check if neither of the 2 nodes are reachable by the other.
You can start with a simple topological sort. Boost's implementation conveniently returns a reverse ordered list of vertices. You can iterate that list, marking each initial leaf node with a new branch id until a shared node is encountered. Demo Time Let's start with the simplests of graph models: #include <boost/graph/adjacency_list.hpp> using Graph = boost::adjacency_list<>; We wish to map branches: using BranchID = int; using BranchMap = std::vector<BranchID>; // maps vertex id -> branch id We want to build, map and visualize the mappings: Graph build(); BranchMap map_branches(Graph const&); void visualize(Graph const&, BranchMap const& branch_map); int main() { // sample data Graph g = build(); // do the topo sort and distinguish branches BranchMap mappings = map_branches(g); // output visualize(g, mappings); } Building Graph Just the sample data from the question: Graph build() { Graph g(4); enum {A,B,C,D}; add_edge(A, B, g); add_edge(A, C, g); add_edge(C, D, g); return g; } Mapping The Branches As described in the introduction: #include <boost/graph/topological_sort.hpp> std::vector<BranchID> map_branches(Graph const& g) { std::vector<Vertex> reverse_topo; boost::topological_sort(g, back_inserter(reverse_topo)); // traverse the output to map to unique branch ids std::vector<BranchID> branch_map(num_vertices(g)); BranchID branch_id = 0; for (auto v : reverse_topo) { auto degree = out_degree(v, g); if (0 == degree) // is leaf? ++branch_id; if (degree < 2) // "unique" path branch_map[v] = branch_id; } return branch_map; } Visualizing Let's write a graph-viz representation with each branch colored: #include <boost/graph/graphviz.hpp> #include <iostream> void visualize(Graph const& g, BranchMap const& branch_map) { // display helpers std::vector<std::string> const colors { "gray", "red", "green", "blue" }; auto name = [](Vertex v) -> char { return 'A'+v; }; auto color = [&](Vertex v) -> std::string { return colors[branch_map.at(v) % colors.size()]; }; // write graphviz: boost::dynamic_properties dp; dp.property("node_id", transform(name)); dp.property("color", transform(color)); write_graphviz_dp(std::cout, g, dp); } This uses a tiny shorthand helper to create the transforming property maps: // convenience short-hand to write transformed property maps template <typename F> static auto transform(F f) { return boost::make_transform_value_property_map(f, boost::identity_property_map{}); }; To compile this on a non-c++14 compiler you can replace the call to transform with the expanded body Full Listing Live On Coliru #include <boost/graph/adjacency_list.hpp> using Graph = boost::adjacency_list<>; using BranchID = int; using BranchMap = std::vector<BranchID>; // maps vertex id -> branch id Graph build(); BranchMap map_branches(Graph const&); void visualize(Graph const&, BranchMap const& branch_map); int main() { // sample data Graph g = build(); // do the topo sort and distinguish branches BranchMap mappings = map_branches(g); // output visualize(g, mappings); } using Vertex = Graph::vertex_descriptor; Graph build() { Graph g(4); enum {A,B,C,D}; add_edge(A, B, g); add_edge(A, C, g); add_edge(C, D, g); return g; } #include <boost/graph/topological_sort.hpp> std::vector<BranchID> map_branches(Graph const& g) { std::vector<Vertex> reverse_topo; boost::topological_sort(g, back_inserter(reverse_topo)); // traverse the output to map to unique branch ids std::vector<BranchID> branch_map(num_vertices(g)); BranchID branch_id = 0; for (auto v : reverse_topo) { auto degree = out_degree(v, g); if (0 == degree) // is leaf? ++branch_id; if (degree < 2) // "unique" path branch_map[v] = branch_id; } return branch_map; } #include <boost/property_map/transform_value_property_map.hpp> // convenience short-hand to write transformed property maps template <typename F> static auto transform(F f) { return boost::make_transform_value_property_map(f, boost::identity_property_map{}); }; #include <boost/graph/graphviz.hpp> #include <iostream> void visualize(Graph const& g, BranchMap const& branch_map) { // display helpers std::vector<std::string> const colors { "gray", "red", "green", "blue" }; auto name = [](Vertex v) -> char { return 'A'+v; }; auto color = [&](Vertex v) -> std::string { return colors[branch_map.at(v) % colors.size()]; }; // write graphviz: boost::dynamic_properties dp; dp.property("node_id", transform(name)); dp.property("color", transform(color)); write_graphviz_dp(std::cout, g, dp); } Printing digraph G { A [color=gray]; B [color=red]; C [color=green]; D [color=green]; A->B ; A->C ; C->D ; } And the rendered graph: Summary Nodes in branches with different colors cannot be compared.
Recording predecessors in a DFS search in an undirected graph
I was trying to use the code from this thread: Boost DFS back_edge, to record the cycles in an undirected graph. To do this I need to store the predecessors for each dfs tree when it finds a back_edge. Since this is an undirected graph I think we can not use directly on_back_edge() from EventVisitor Concept. So I was thinking to record the predecessors in the void back_edge() method of below code. But I am not sure how to do this and return the cycle vertices. Here is the code and the part I added for recording predecessors: #include <boost/config.hpp> #include <boost/graph/adjacency_list.hpp> #include <boost/graph/depth_first_search.hpp> namespace { using namespace boost; typedef adjacency_list<vecS, vecS, undirectedS, no_property, property<edge_weight_t, int> > Graph; typedef graph_traits<Graph>::edge_descriptor Edge; typedef std::set<Edge> EdgeSet; } struct MyVisitorCycle : default_dfs_visitor { MyVisitorCycle(EdgeSet &tree_edges, vector<vector<vertex_t> > &cycles1) : tree_edges(tree_edges), cycles(cycles1) {} void tree_edge(Edge e, const Graph& g) const { std::cerr << "tree_edge " << e << std::endl; tree_edges.insert(e); } void back_edge(Edge e, const Graph& g) const { if (tree_edges.find(e) == tree_edges.end()) std::cerr << "back_edge " << e << std::endl; /// I added this part vertex_t s = vertex (0,g); std::vector <vertex_t> p(num_vertices (g)); depth_first_search (g, s, visitor (boost::record_predecessors (&p[0], on_tree_edge())));/// here has problem p.push_back (target (e,g)); /// close the cycle cycles.push_back (p); } private: EdgeSet& tree_edges; vector<vector <vertex_t> > &cycles; }; int main() { Graph g; add_edge(0, 1, g); add_edge(1, 2, g); add_edge(2, 3, g); add_edge(3, 0, g); add_edge(1, 4, g); add_edge(4, 5, g); add_edge(5, 6, g); add_edge(6, 3, g); add_edge(2, 5, g); std::set<Edge> tree_edges; vector<vector <vertex_t> > cycles; MyVisitorCycle vis(tree_edges, cycles); depth_first_search(g, visitor(vis)); }
Here's a quick post, I'll come back with some notes later Live On Coliru #include <iostream> #include <boost/config.hpp> #include <boost/graph/adjacency_list.hpp> #include <boost/graph/depth_first_search.hpp> namespace { using namespace boost; typedef adjacency_list<vecS, vecS, undirectedS, no_property, property<edge_weight_t, int> > Graph; typedef graph_traits<Graph>::edge_descriptor Edge; typedef std::set<Edge> EdgeSet; } struct MyVisitor : default_dfs_visitor { MyVisitor(EdgeSet& tree_edges) : tree_edges(tree_edges) {} void tree_edge(Edge e, const Graph& g) const { std::cerr << "tree_edge " << e << std::endl; tree_edges.insert(e); } void back_edge(Edge e, const Graph& g) const { if (tree_edges.find(e) == tree_edges.end()) std::cerr << "back_edge " << e << std::endl; } private: EdgeSet& tree_edges; }; int main() { Graph g; add_edge(0, 1, g); add_edge(0, 2, g); std::set<Edge> tree_edges; MyVisitor vis(tree_edges); std::vector<Graph::vertex_descriptor> pred(num_vertices(g), Graph::null_vertex()); depth_first_search(g, visitor(boost::make_dfs_visitor( boost::record_predecessors(pred.data(), boost::on_back_edge{}) ))); for (auto v : make_iterator_range(vertices(g))) { std::cout << "Predecessor for " << v << " is " << pred.at(v) << "\n"; } } Prints Predecessor for 0 is 2 Predecessor for 1 is 18446744073709551615 Predecessor for 2 is 18446744073709551615
Unique set of undirected edges
I want to create a set (mathematically speaking, not std::set) of unique elements in C++. My elements are std::pair<int, int> and they represent an edge. Because those edges are not directed, I don't want to have duplicates like (3,4) and (4,3). How can I achieve this in C++ ?
Something along these lines, perhaps: using Edge = std::pair<int, int>; struct CompareEdges { bool operator()(const Edge& a, const Edge& b) const { return Normalize(a) < Normalize(b); } private: Edge Normalize(const Edge& e) { if (e.first <= e.second) return e; return {e.second, e.first}; } }; std::set<Edge, CompareEdges> SetOfEdges;
This is another solution, with the compare function as lambda expression. using Edge = pair<int, int>; std::set<Edge, std::function<bool(const Edge &, const Edge &)>> edges( [](const Edge &a, const Edge &b) { const int x = min(a.first, a.second); const int y = min(b.first, b.second); if (x < y) return true; else if (y > x) return false; else return max(a.first, a.second) < max(b.first, b.second); } );
Is it possible to have several edge weight property maps for one graph?
How would I create a graph, such that the property map (weight of edges) is different in each property map? Is it possible to create such a property map? Like an array of property maps? I have not seen anyone on the Internet using it, could I have an example? Graph g(10); // graph with 10 nodes cin>>a>>b>>weight1>>weight2>>weight3>>weight4; and put each weight in a property map.
You can compose a property map in various ways. The simplest approach would seem something like: Using C++11 lambdas with function_property_map Live On Coliru #include <boost/property_map/function_property_map.hpp> #include <iostream> struct weights_t { float weight1, weight2, weight3, weight4; }; using namespace boost; int main() { std::vector<weights_t> weight_data { // index is vertex id { 1,2,3,4 }, { 5,6,7,8 }, { 9,10,11,12 }, { 13,14,15,16 }, }; auto wmap1 = make_function_property_map<unsigned, float>([&weight_data](unsigned vertex_id) { return weight_data.at(vertex_id).weight1; }); auto wmap2 = make_function_property_map<unsigned, float>([&weight_data](unsigned vertex_id) { return weight_data.at(vertex_id).weight2; }); auto wmap3 = make_function_property_map<unsigned, float>([&weight_data](unsigned vertex_id) { return weight_data.at(vertex_id).weight3; }); auto wmap4 = make_function_property_map<unsigned, float>([&weight_data](unsigned vertex_id) { return weight_data.at(vertex_id).weight4; }); for (unsigned vertex = 0; vertex < weight_data.size(); ++vertex) std::cout << wmap1[vertex] << "\t" << wmap2[vertex] << "\t" << wmap3[vertex] << "\t"<< wmap4[vertex] << "\n"; } Using C++03 with transform_value_property_map This is mainly much more verbose: Live On Coliru #include <boost/property_map/transform_value_property_map.hpp> #include <iostream> struct weights_t { float weight1, weight2, weight3, weight4; weights_t(float w1, float w2, float w3, float w4) : weight1(w1), weight2(w2), weight3(w3), weight4(w4) { } template <int which> struct access { typedef float result_type; float operator()(weights_t const& w) const { BOOST_STATIC_ASSERT(which >= 1 && which <= 4); switch (which) { case 1: return w.weight1; case 2: return w.weight2; case 3: return w.weight3; case 4: return w.weight4; } } }; }; using namespace boost; int main() { std::vector<weights_t> weight_data; // index is vertex id weight_data.push_back(weights_t(1,2,3,4)); weight_data.push_back(weights_t(5,6,7,8)); weight_data.push_back(weights_t(9,10,11,12)); weight_data.push_back(weights_t(13,14,15,16)); boost::transform_value_property_map<weights_t::access<1>, weights_t*, float> wmap1 = make_transform_value_property_map(weights_t::access<1>(), &weight_data[0]); boost::transform_value_property_map<weights_t::access<2>, weights_t*, float> wmap2 = make_transform_value_property_map(weights_t::access<2>(), &weight_data[0]); boost::transform_value_property_map<weights_t::access<3>, weights_t*, float> wmap3 = make_transform_value_property_map(weights_t::access<3>(), &weight_data[0]); boost::transform_value_property_map<weights_t::access<4>, weights_t*, float> wmap4 = make_transform_value_property_map(weights_t::access<4>(), &weight_data[0]); for (unsigned vertex = 0; vertex < weight_data.size(); ++vertex) std::cout << wmap1[vertex] << "\t" << wmap2[vertex] << "\t" << wmap3[vertex] << "\t"<< wmap4[vertex] << "\n"; } Output Both samples output 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16