Related
I have the following piece of code:
struct Vertex {
std::string name;
};
struct Edge {
std::string name;
};
class MyGraph {
public:
MyGraph() = default;
using Graph = adjacency_list<vecS, vecS, bidirectionalS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
using VertexDesc = graph_traits<Graph>::vertex_descriptor;
using EdgeDesc = graph_traits<Graph>::edge_descriptor;
void addNode(std::shared_ptr<Vertex> node){
const auto name = node->name;
if (vertexMap_.find(name) == vertexMap_.end()) {
const auto vertex = add_vertex(node, graph_);
vertexLabelArray_.emplace_back(name);
vertexMap_[name] = vertex;
}
}
void addEdge(std::shared_ptr<Vertex> src, std::shared_ptr<Vertex> dst, std::shared_ptr<Edge> weight = nullptr) {
const auto srcName = src->name;
const auto dstName = dst->name;
const auto vertexPair = std::make_pair(srcName, dstName);
if (edgeSet_.find(vertexPair) == edgeSet_.end()) {
addNode(src);
addNode(dst);
const auto edge = add_edge(vertexMap_[srcName], vertexMap_[dstName], weight, graph_).first;
edgeSet_.insert(vertexPair);
edgeLabelMap_[edge] = weight ? weight->name : "";
}
}
void print(std::ostream& out)
{
write_graphviz(out, graph_,
make_label_writer(vertexLabelArray_.data()),
make_label_writer(make_assoc_property_map(edgeLabelMap_)));
}
private:
std::vector<std::string> vertexLabelArray_;
std::map<EdgeDesc, std::string> edgeLabelMap_;
std::map<std::string, VertexDesc> vertexMap_;
std::set<std::pair<std::string, std::string>> edgeSet_;
Graph graph_;
};
struct Node : Vertex {};
struct Arc : Edge {};
int main() {
MyGraph g;
const auto n1 = std::make_shared<Node>(Node{{"n1"}});
const auto n2 = std::make_shared<Node>(Node{{"n2"}});
const auto e1 = std::make_shared<Arc>(Arc{{"e1"}});
g.addEdge(n1, n2, e1);
g.print(std::cout);
}
I would like to reduce some of the potential redundancies:
1 - how can i use setS instead of vecS to avoid checking is a vertex/edge exists. when i do so, the write_graphiz function complains that it fails with lots of errors beginning with
error: cannot form a reference to 'void' typedef const value_type& const_reference;
2 - i am using a shared_ptr for allowing for custom object to be attached to vertices and edges. is there an easy way for looking up a vertex index based on its attached object?
3 - Is it possible to remove most of the external data structures and use boost property instead somehow?
the whole thing is uploaded here: https://godbolt.org/z/qbEnEoYj3
any help is appreciated.
1 - how can i use setS instead of vecS to avoid checking is a
Q.: vertex/edge exists. when i do so, the write_graphiz function complains
that it fails with lots of errors beginning with
Sure. In fact the edge selector is no problem to begin with: https://godbolt.org/z/b1xbYMM4s. Making the vertex containter setS won't do what you mean, because every added vertex is unique by definition.
But in the interest of showing the technical issue at hand: there is no implicit vertex index (ordinal numbering [0..numvertices()) for that storage strategy. Some algorithms require one. Also, you assume one with VertexLabelArray_ which no longer makes sense, so let's change into VertexLabelMap_ just like for edge labels:
Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
struct Vertex {
std::string name;
};
struct Edge {
std::string name;
};
class MyGraph {
public:
MyGraph() = default;
using Graph = boost::adjacency_list< //
boost::setS, boost::listS, boost::bidirectionalS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
using VertexDesc = boost::graph_traits<Graph>::vertex_descriptor;
using EdgeDesc = boost::graph_traits<Graph>::edge_descriptor;
void addNode(std::shared_ptr<Vertex> node)
{
const auto name = node->name;
if (vertexMap_.find(name) == vertexMap_.end()) {
const auto vertex = add_vertex(node, graph_);
vertexLabelMap_.emplace(vertex, name);
vertexMap_[name] = vertex;
}
}
void addEdge(std::shared_ptr<Vertex> src, std::shared_ptr<Vertex> dst,
std::shared_ptr<Edge> weight = nullptr)
{
const auto srcName = src->name;
const auto dstName = dst->name;
const auto vertexPair = std::make_pair(srcName, dstName);
if (edgeSet_.find(vertexPair) == edgeSet_.end()) {
addNode(src);
addNode(dst);
const auto edge = add_edge(vertexMap_[srcName], vertexMap_[dstName],
weight, graph_)
.first;
edgeSet_.insert(vertexPair);
edgeLabelMap_[edge] = weight ? weight->name : "";
}
}
void print(std::ostream& out)
{
std::map<VertexDesc, int> idmap;
for (auto v : boost::make_iterator_range(vertices(graph_))) {
idmap.emplace(v, idmap.size());
}
write_graphviz(out, graph_,
boost::make_label_writer(
boost::make_assoc_property_map(vertexLabelMap_)),
boost::make_label_writer(
boost::make_assoc_property_map(edgeLabelMap_)),
boost::default_writer{},
boost::make_assoc_property_map(idmap));
}
private:
std::map<VertexDesc, std::string> vertexLabelMap_;
std::map<EdgeDesc, std::string> edgeLabelMap_;
std::map<std::string, VertexDesc> vertexMap_;
std::set<std::pair<std::string, std::string>> edgeSet_;
Graph graph_;
};
struct Node : Vertex {};
struct Arc : Edge {};
#include <iostream>
int main() {
MyGraph g;
const auto n1 = std::make_shared<Node>(Node{{"n1"}});
const auto n2 = std::make_shared<Node>(Node{{"n2"}});
const auto e1 = std::make_shared<Arc>(Arc{{"e1"}});
g.addEdge(n1, n2, e1);
g.print(std::cout);
}
Alternatively, if such an index can be made part of Vertex:
Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
struct Vertex {
int id;
std::string name;
};
struct Edge {
std::string name;
};
class MyGraph {
public:
MyGraph() = default;
using Graph = boost::adjacency_list< //
boost::setS, boost::listS, boost::bidirectionalS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
using VertexDesc = boost::graph_traits<Graph>::vertex_descriptor;
using EdgeDesc = boost::graph_traits<Graph>::edge_descriptor;
void addNode(std::shared_ptr<Vertex> const& node)
{
const auto name = node->name;
if (vertexMap_.find(name) == vertexMap_.end()) {
const auto vertex = add_vertex(node, graph_);
vertexLabelArray_.push_back(name);
vertexMap_[name] = vertex;
}
}
void addEdge(std::shared_ptr<Vertex> const& src,
std::shared_ptr<Vertex> const& dst,
std::shared_ptr<Edge> const& weight = nullptr)
{
const auto srcName = src->name;
const auto dstName = dst->name;
const auto vertexPair = std::make_pair(srcName, dstName);
if (edgeSet_.find(vertexPair) == edgeSet_.end()) {
addNode(src);
addNode(dst);
const auto edge = add_edge(vertexMap_[srcName], vertexMap_[dstName],
weight, graph_)
.first;
edgeSet_.insert(vertexPair);
edgeLabelMap_[edge] = weight ? weight->name : "";
}
}
void print(std::ostream& out)
{
auto idmap = boost::make_transform_value_property_map(
[](auto const& sp) { return sp->id; },
get(boost::vertex_bundle, graph_));
write_graphviz(
out, graph_,
boost::make_label_writer(boost::make_iterator_property_map(
vertexLabelArray_.begin(), idmap)),
boost::make_label_writer(
boost::make_assoc_property_map(edgeLabelMap_)),
boost::default_writer{}, idmap);
}
private:
std::vector<std::string> vertexLabelArray_;
std::map<EdgeDesc, std::string> edgeLabelMap_;
std::map<std::string, VertexDesc> vertexMap_;
std::set<std::pair<std::string, std::string>> edgeSet_;
Graph graph_;
};
struct Node : Vertex {};
struct Arc : Edge {};
#include <iostream>
int main() {
MyGraph g;
const auto n1 = std::make_shared<Node>(Node{{0, "n1"}});
const auto n2 = std::make_shared<Node>(Node{{1, "n2"}});
const auto e1 = std::make_shared<Arc>(Arc{{"e1"}});
g.addEdge(n1, n2, e1);
g.print(std::cout);
}
Q.: 2 - i am using a shared_ptr for allowing for custom object to be
attached to vertices and edges.
I noticed this. I think it's a little bit questionable. If you're looking for efficiency, I wouldn't be using shared_ptr all over the place.
Q.: is there an easy way for looking up a
vertex index based on its attached object?
Not built in. There is the (undocumented?) labeled_graph adaptor that has... some convenience. YMMV. Also, you can use a bimap or similar.
Q.: 3 - Is it possible to remove most of the external data structures and
use boost property instead somehow?
I would strongly consider this. Of course you can. Basically, you already do this.
Review
Your code has many opportunities for improvement (including effective optimizations).
pass the shared pointers by const&:
do not pass shared pointers in the first place, since you're not intending to share ownership/observe refcounts/lifetimes. I'll show this when I show a version that embodies the node/edge data in the graph model directly.
void addNode could return the vertex descriptor, avoiding a lot of redundant lookups. This also makes it more explicit about an item already existing not being an error. (Right now it simply ignores the addNode/addEdge?)
void ensureEdge(std::shared_ptr<Vertex> const& src,
std::shared_ptr<Vertex> const& dst,
std::shared_ptr<Edge> const& edge = {})
{
if (edgeSet_.emplace(src->name, dst->name).second) {
auto [descriptor, isnew] = add_edge( //
ensureNode(src), ensureNode(dst), edge, graph_);
edgeLabelMap_.emplace(descriptor, edge ? edge->name : "");
}
}
Doing less work means being more efficient.
Like you almost mentioned you can already test whether an edge exists using the result of add_edge, obviating the extra edgeSet_:
EdgeDesc ensureEdge(std::shared_ptr<Vertex> const& src,
std::shared_ptr<Vertex> const& dst,
std::shared_ptr<Edge> const& edge = {})
{
auto [descriptor, isnew] =
add_edge(ensureNode(src), ensureNode(dst), edge, graph_);
if (isnew)
edgeLabelMap_.emplace(descriptor, edge ? edge->name : "");
return descriptor;
}
Note that now, because the behaviour is more consistent, we can also return the edge descriptor. This will remove the need to query for the edge we just added.
Current version Live On Compiler Explorer:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
struct Vertex { std::string name; };
struct Edge { std::string name; };
class MyGraph {
public:
MyGraph() = default;
using Graph = boost::adjacency_list< //
boost::setS, boost::vecS, boost::bidirectionalS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
using VertexDesc = Graph::vertex_descriptor;
using EdgeDesc = Graph::edge_descriptor;
VertexDesc ensureNode(std::shared_ptr<Vertex> const& node)
{
auto const& name = node->name;
auto it = vertexMap_.find(name);
if (it == vertexMap_.end()) {
auto descriptor = add_vertex(node, graph_);
vertexLabelArray_.push_back(name);
vertexMap_[name] = descriptor;
it = vertexMap_.emplace(name, descriptor).first;
}
return it->second;
}
EdgeDesc ensureEdge(std::shared_ptr<Vertex> const& src,
std::shared_ptr<Vertex> const& dst,
std::shared_ptr<Edge> const& edge = {})
{
auto [descriptor, isnew] =
add_edge(ensureNode(src), ensureNode(dst), edge, graph_);
if (isnew)
edgeLabelMap_.emplace(descriptor, edge ? edge->name : "");
return descriptor;
}
void print(std::ostream& out) const
{
write_graphviz(
out, graph_, //
boost::make_label_writer(vertexLabelArray_.data()),
boost::make_label_writer(make_assoc_property_map(edgeLabelMap_)));
}
private:
std::vector<std::string> vertexLabelArray_;
std::map<EdgeDesc, std::string> edgeLabelMap_;
std::map<std::string, VertexDesc> vertexMap_;
Graph graph_;
};
struct Node : Vertex {};
struct Arc : Edge {};
#include <iostream>
int main() {
MyGraph g;
const auto n1 = std::make_shared<Node>(Node{{"n1"}});
const auto n2 = std::make_shared<Node>(Node{{"n2"}});
const auto e1 = std::make_shared<Arc>(Arc{{"e1"}});
g.ensureEdge(n1, n2, e1);
g.print(std::cout);
}
Still prints
digraph G {
0[label=n2];
1[label=n1];
1->0 [label=e1];
}
Other Steps
You specified BidirectionalGraph support, but don't currently use the in-edge interface. Consider dropping that so you don't incur the overhead for maintaining the redundant edge information
using Graph = boost::adjacency_list< //
boost::setS, boost::vecS, boost::directedS,
std::shared_ptr<Vertex>, std::shared_ptr<Edge>>;
Consider using value semantics for the property bundles. This will reduce allocations, increase locality of reference and may enable a host of storage optimization opportunities.
Consider this version, which no longer uses vertexLabelArray_ nor edgeLabelMap_ (both properties simply reside inside the graph model after all):
void print(std::ostream& out) const {
write_graphviz(out, graph_,
make_label_writer(get(&Vertex::name, graph_)),
make_label_writer(get(&Edge::name, graph_)));
}
Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
struct Vertex { std::string name; };
struct Edge { std::string name; };
class MyGraph {
public:
using Graph = boost::adjacency_list< //
boost::setS, boost::vecS, boost::directedS, Vertex, Edge>;
using VertexDesc = Graph::vertex_descriptor;
using EdgeDesc = Graph::edge_descriptor;
VertexDesc ensureNode(Vertex const& node) {
if (auto it = vertexMap_.find(node.name); it != vertexMap_.end())
return it->second;
auto descriptor = add_vertex(node, graph_);
vertexMap_[node.name] = descriptor;
vertexMap_.emplace(node.name, descriptor);
return descriptor;
}
EdgeDesc ensureEdge(Vertex const& src, Vertex const& dst, Edge edge) {
auto [descriptor, isnew] =
add_edge(ensureNode(src), ensureNode(dst), std::move(edge), graph_);
return descriptor; // TODO maybe raise an error if !isnew?
}
void print(std::ostream& out) const {
write_graphviz(out, graph_,
make_label_writer(get(&Vertex::name, graph_)),
make_label_writer(get(&Edge::name, graph_)));
}
private:
std::map<std::string, VertexDesc> vertexMap_;
Graph graph_;
};
struct Node : Vertex { };
struct Arc : Edge { };
#include <iostream>
int main() {
MyGraph g;
Node n1{{"n1"}}, n2{{"n2"}};
Arc e1{{"e1"}};
g.ensureEdge(n1, n2, e1);
g.ensureEdge({"n3"}, {"n4"}, {"e2"});
g.print(std::cout);
}
Prints
digraph G {
0[label=n2];
1[label=n1];
2[label=n4];
3[label=n3];
1->0 [label=e1];
3->2 [label=e2];
}
Much More Efficient Vertex Lookup?
If you really care about storage and lookup efficiency, make vertexMap_ key a string_view. This requires careful thought about the lifetime of the referred-to strings.
// use stored property, not parameter as vertexMap_ key!
vertexMap_.emplace(graph_[vd].name, vd);
For starters, you NEED a container selector with reference stabiility for the vertex container (e.g. listS).
Also, consider making the container a flat_map so storage and lookup will be much optimized. They will effectively become vector<tuple<char const*, size_t, size_t> >:
boost::container::flat_map<std::string_view, VertexDesc> vertexMap_;
See It Live On Compiler Explorer
#include <boost/container/flat_map.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
struct Vertex { std::string name; };
struct Edge { std::string name; };
class MyGraph {
public:
using Graph = boost::adjacency_list< //
boost::setS, boost::listS, boost::directedS, Vertex, Edge>;
using VertexDesc = Graph::vertex_descriptor;
using EdgeDesc = Graph::edge_descriptor;
VertexDesc ensureNode(Vertex const& node) {
if (auto it = vertexMap_.find(node.name); it != vertexMap_.end())
return it->second;
auto vd = add_vertex(node, graph_);
// use stored property, not parameter as vertexMap_ key!
vertexMap_.emplace(graph_[vd].name, vd);
return vd;
}
EdgeDesc ensureEdge(Vertex const& src, Vertex const& dst, Edge edge) {
auto [ed, isnew] =
add_edge(ensureNode(src), ensureNode(dst), std::move(edge), graph_);
return ed; // TODO maybe raise an error if !isnew?
}
void print(std::ostream& out) const {
write_graphviz(out, graph_,
make_label_writer(get(&Vertex::name, graph_)),
make_label_writer(get(&Edge::name, graph_)),
boost::default_writer{},
get(&Vertex::name, graph_));
}
private:
boost::container::flat_map<std::string_view, VertexDesc> vertexMap_;
Graph graph_;
};
struct Node : Vertex { };
struct Arc : Edge { };
#include <iostream>
int main() {
MyGraph g;
Node n1{{"n1"}}, n2{{"n2"}};
Arc e1{{"e1"}};
g.ensureEdge(n1, n2, e1);
g.ensureEdge({"n3"}, {"n4"}, {"e2"});
g.print(std::cout);
}
Note this cheated a little by using the Vertex name as the graphviz node ID. That makes a lot of sense anyways:
digraph G {
n2[label=n2];
n1[label=n1];
n4[label=n4];
n3[label=n3];
n1->n2 [label=e1];
n3->n4 [label=e2];
}
If you insist on integral IDs - you can hack them back using the fact that vertexMap_ is now contiguous:
void print(std::ostream& out) const {
auto node_id = boost::make_function_property_map<VertexDesc>(
[this](VertexDesc vd) {
return vertexMap_.find(graph_[vd].name) - vertexMap_.begin();
});
write_graphviz(out, graph_,
make_label_writer(get(&Vertex::name, graph_)),
make_label_writer(get(&Edge::name, graph_)),
boost::default_writer{}, node_id);
}
This prints (Live Link):
digraph G {
1[label=n2];
0[label=n1];
3[label=n4];
2[label=n3];
0->1 [label=e1];
2->3 [label=e2];
}
Note that doing the listS vertex container selection makes a BIG difference in resource trade-offs in the graph implementation itself, so if the vertex lookup via vertexMap_ is not your bottleneck, don't do this optimization!
More BGL facilities
As I mentioned there's some supported for labeled graphs in BGL. I'm not recommending that as I've run into my share of implementation bugs/quirks. But I would be remiss not to mention it here.
Also see How to create a named_graph with Boost Graph Library? which is a very good answer
Having trouble with unordered_map named visited.
The goal of visited is to mark visited vertices in graph which are pairs of char and int.
The problem is that values in visited change itself even if I changed them manually after the initialization of visited.
I tried simpler method to declare hash for pair of int and char but it didn't work.
Then I tried to change pair of char and int to int, by multiplicating char by 1M and adding int.
However it didn't work either.
Details:
When function BFS is called again then the same key (i.e. key is pair<'c', 1> converted to int)
is called in condition:
if ( visited[(pair.first - '0')*1000000 + pair.second] == 0 )
then the condition is passed even if I assign to this key a value of 1 earlier.
#include <vector>
#include <unordered_map>
#include <list>
#include <utility>
using namespace std;
typedef std::pair<char, int> p;
// define hash for pair<char, int>
struct pair_hash
{
template <class T1, class T2>
std::size_t operator() (const std::pair<T1, T2> &p) const
{
return std::hash<T1>()(p.first) ^ std::hash<T2>()(p.second);
}
};
void BFS(pair<char, int> v,
unordered_map<int, int> visited,
unordered_map<pair<char, int>, vector<pair<char, int>>, pair_hash> graph) {
// Maybe int instead of a pair<char, int> as a key will work...
int x = (v.first - '0')*1000000 + v.second;
if (visited[x] == 0) {
// Create a queue for BFS
list<pair<char, int>> queue;
// Mark the current node as visited and enqueue it
visited[x] = 1;
queue.push_back(v);
while(!queue.empty()) {
// Dequeue a vertex from queue
v = queue.front();
queue.pop_front();
// Get all adjacent vertices of the dequeued
// vertex s. If a adjacent has not been visited,
// then mark it visited and enqueue it
for (p P : graph[v]) {
int y = (P.first - '0')*1000000 + P.second;
// Problem lays here
if (visited[y] == 0) {
// now I set converted pair<'c', 1> to int to 1:
visited[y] = 1;
// After 1, 2 calls of function named BFS the condtion
// if ( visited[ converted pair<'c', 1> to int ] == 0 )
// is passed even if I set it to 1 earlier
queue.push_back(P);
}
}
}
}
}
int main() {
unordered_map<pair<char, int>, vector<pair<char, int>>, pair_hash> graph;
// Container for marking vertices as visited
unordered_map<int, int> visited;
for (auto kv: graph) {
int x = (kv.first.first - '0')*1000000 + kv.first.second;
visited[x] = 0;
}
// Graph traversal
for (auto kv: graph) {
BFS(kv.first, visited, graph);
}
}
return 0;
}
I need to run A* on a graph with some of the edges removed. To do so I construct a filtered graph with blacklisted edges and I want to run A* on the filtered graph. The blacklisted edges are embedded in the class BlackListEdgeConstraint, that I initialize by passing to its constructor the list of the forbidden edges. This BlackListEdgeConstraint is correctly constructed, and I construct the filtered graph with these constrain. The problem is that when I run A* on the filtered graph, another BlackListEdgeConstraint object is mysteriously constructed with an empty constructor, and no edge is in effect blacklisted. Why is it happening?
This is my complete code:
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/astar_search.hpp>
using namespace std;
typedef std::pair<int, int> Edge;
typedef boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, boost::property<boost::edge_weight_t, int> > graph_t;
typedef boost::graph_traits<graph_t>::vertex_descriptor vertex_descriptor;
struct BlackListEdgeConstraint
{
private:
std::set<Edge> blackList;
graph_t* g;
public:
BlackListEdgeConstraint():blackList(std::set<Edge>() ),g(NULL){throw std::runtime_error("This should not be called");};
BlackListEdgeConstraint(std::set<Edge>& list, graph_t* g_) : blackList(list), g(g_)
{
Edge edge = *blackList.begin();
std::cout<<"The black list contains "<< edge.first<<"-"<<edge.second<<std::endl;
}
/**
* This is the "predicate function" used by boost::filtered_graph (
* see http://www.boost.org/doc/libs/1_64_0/libs/graph/doc/filtered_graph.html )
* It it returns true, the edge is included in the filtered graph, otherwise it is excluded.
*/
bool operator()(const boost::graph_traits<graph_t>::edge_descriptor& e) const
{
Edge edge(source(e,*g), target(e,*g) );
std::cout<<"Should we include edge "<<source(e,*g)<<" ->"<< target(e,*g)<<" represented by a descriptor "<<e<<"? ";
//Include the edge if it's not in the blacklist.
bool include = (blackList.find( edge ) == blackList.end() );
std::cout<<include<<std::endl;
return include;
}
};
template<class GraphType>
class MyHeuristic : public boost::astar_heuristic<GraphType, double>
{
private:
const GraphType* g;
public:
MyHeuristic(const GraphType* g_): g(g_) {};
double operator()(vertex_descriptor v)
{
return 0;
}
};
//Used to terminate our search
struct GoalsReached{};
class MyVisitor : public boost::default_astar_visitor
{
private:
vertex_descriptor goal;
public:
MyVisitor(vertex_descriptor goal_): goal(goal_){};
template<class GraphType>
void examine_vertex(vertex_descriptor u, const GraphType& g)
{ if (u==goal) throw GoalsReached(); }
};
int main()
{
Edge edge_array[] = {Edge(0,1), Edge(1,2), Edge(2,3), Edge(3,0), Edge(1,3) };
int weights[] = {1,1,1,1,1};
int num_arcs = sizeof(edge_array) / sizeof(Edge);
int num_nodes = 4;
// Graph created from the list of edges
graph_t g(edge_array, edge_array + num_arcs, weights, num_nodes);
// Create descriptor for the source node
vertex_descriptor s = vertex(0, g);
vertex_descriptor goal = vertex(3,g) ;
std::set<Edge> blacklist; blacklist.insert( Edge(1,3) );
BlackListEdgeConstraint filter(blacklist, &g);
boost::filtered_graph<graph_t, BlackListEdgeConstraint> filtered(g, filter);
cout<<"filtered constructed"<<endl;
// Create vectors to store the predecessors (p) and the distances from the root (d)
std::vector<vertex_descriptor> p(num_vertices(filtered));
std::vector<int> d(num_vertices(filtered));
try{
cout<<"Launching astar_search"<<endl;
boost::astar_search(filtered, s,
MyHeuristic<boost::filtered_graph<graph_t, BlackListEdgeConstraint>>(&filtered),
boost::predecessor_map(&p[0]).
distance_map(&d[0]).
visitor(MyVisitor(goal) )
);
cout<<"astar_search launched"<<endl;
}catch(const GoalsReached& )
{ // Print the path
std::vector<boost::graph_traits<graph_t>::vertex_descriptor > path;
boost::graph_traits<graph_t>::vertex_descriptor current = goal;
while(current!=s)
{
path.push_back(current);
current = p[current];
}
path.push_back(s);
// Prints the path obtained in reverse
std::vector<boost::graph_traits<graph_t>::vertex_descriptor >::reverse_iterator it;
for (it = path.rbegin(); it != path.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
return EXIT_SUCCESS;
}
And this is the output:
The black list contains 1-3
filtered constructed
Launching astar_search
terminate called after throwing an instance of 'std::runtime_error'
what(): This should not be called
My boost version is 1.54
The problem is that, when boost::astar_search(..) is invoked, the empty constructor BlackListEdgeConstraint() is called.
I don't know how you reach the conclusion. I cannot reproduce that:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/astar_search.hpp>
struct VertexProperties {
};
struct EdgeProperties {
double weight = 1;
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexProperties, EdgeProperties> MyGraph;
struct StreetDirectory {
using Graph = MyGraph;
using Edge = Graph::edge_descriptor;
using Vertex = Graph::vertex_descriptor;
};
struct BlackListEdgeConstraint : StreetDirectory
{
private:
std::set<StreetDirectory::Edge> blackList;
public:
BlackListEdgeConstraint(const std::set<Edge>& list) : blackList(list) {};
BlackListEdgeConstraint()
{
throw std::runtime_error("This should not be called");
};
bool operator()(const Edge& e) const
{
//Include the edge if it's not in the blacklist.
return blackList.find(e) == blackList.end();
}
};
int main() {
MyGraph graph;
const std::set<StreetDirectory::Edge> blacklistEdges {
add_edge(1,2,graph).first,
add_edge(1,3,graph).first,
add_edge(2,4,graph).first,
};
add_edge(4,2,graph);
BlackListEdgeConstraint filter(blacklistEdges);
boost::filtered_graph<MyGraph, BlackListEdgeConstraint> filtered(graph, filter);
std::vector<StreetDirectory::Vertex> p(boost::num_vertices(filtered)); //Output variable
std::vector<double> d(boost::num_vertices(filtered)); //Output variable
boost::default_astar_visitor vis;
boost::astar_search(
filtered,
1,
[](auto /*vd*/) { return 1; },
boost::predecessor_map(&p[0]).
weight_map(boost::get(&EdgeProperties::weight, filtered)).
distance_map(&d[0]).
visitor(vis)
);
}
Notes
in general functors are passed by value in (standard) library algorithms. So you'd use std::reference_wrapper<BlackListEdgeConstraint> if you wanted to use the same instance. But like I said, I don't see it happening, so it's not a problem AFAICT
in your sample there seemed to be no edge-weight map indicated. I don't see how that should have compiled
I am new in Boost and I'm trying to write a program to Dijkstra-SP and Dijkstra with A*. The graph take random edge weight between [0,100]. I have many problems as we can see and I need some suggestions in order to solve it.
#include <boost/graph/grid_graph.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <iostream>
typedef boost::grid_graph<2> grid;
typedef boost::graph_traits<grid>::vertices_size_type vertices_size_type;
typedef boost::property_map<grid, boost::vertex_index_t>::const_type GridIndexMapType;
GridIndexMapType gridIndexMap(get(boost::vertex_index, graph));
shared_array_property_map < vertex_descriptor, property_map<graph_t, vertex_index_t>::const_type>
p_map(num_vertices(graph), get(vertex_index, graph));
shared_array_property_map<double, property_map<graph_t, vertex_index_t>::const_type>
d_map(num_vertices(graph), get(vertex_index, graph));
struct edge_weight_map;
namespace boost
{
template<>
struct property_map< graph_t, edge_weight_t > {
typedef edge_weight_map type;
typedef edge_weight_map const_type;
};
}
/*
Map from edges to weight values
*/
struct edge_weight_map
{
typedef double value_type;
typedef value_type reference;
typedef edge_descriptor key_type;
typedef boost::readable_property_map_tag category;
const graph_t& m_graph;
edge_weight_map(const graph_t& g)
:m_graph(g) { }
reference operator[](key_type e) const; // implemented below
};
typedef boost::property_map<graph_t, boost::edge_weight_t>::const_type
const_edge_weight_map;
typedef boost::property_traits<const_edge_weight_map>::reference
edge_weight_map_value_type;
typedef boost::property_traits<const_edge_weight_map>::key_type
edge_weight_map_key;
namespace boost{
// PropertyMap valid expressions
edge_weight_map_value_type get(const_edge_weight_map pmap,
edge_weight_map_key e) {
return pmap[e]; }
// ReadablePropertyGraph valid expressions
const_edge_weight_map get(boost::edge_weight_t, const graph_t&g) {
return const_edge_weight_map(g); }
edge_weight_map_value_type get(boost::edge_weight_t tag, const graph_t& g,
edge_weight_map_key e)
{ return get(tag, g)[e]; }
}
edge_weight_map::reference
edge_weight_map::operator[](key_type e) const {
vertex_descriptor t = target(e,m_graph);
vertex_descriptor s = source(e,m_graph);
// f = f(t,s)
return f;
}
class grid_graph {
public:
friend std::ostream& operator<<(std::ostream&, const grid_graph&);
//friend grid_graph random_maze(std::size_t, std::size_t);
grid_graph():m_grid(create_grid(0, 0)) {};
grid_graph(std::size_t x, std::size_t y):m_grid(create_grid(x, y)){};
// The length of the grid_graph along the specified dimension.
vertices_size_type length(std::size_t d) const {return m_grid.length(d);}
private:
// Create the underlying rank-2 grid with the specified dimensions.
grid create_grid(std::size_t x, std::size_t y) {
boost::array<std::size_t, 2> lengths = { {x, y} };
return grid(lengths);
}
// The grid underlying the grid_graph
grid m_grid;
};
// Euclidean heuristic for a grid
//
// This calculates the Euclidean distance between a vertex and a goal
// vertex.
class euclidean_heuristic {
// public boost::astar_heuristic<filtered_grid, double>
public:
euclidean_heuristic(vertex_descriptor goal):m_goal(goal) {};
double operator()(vertex_descriptor v) {
return sqrt(pow(m_goal[0] - v[0], 2) + pow(m_goal[1] - v[1], 2));
}
private:
vertex_descriptor m_goal;
};
// Exception thrown when the goal vertex is found
struct found_goal {};
// Visitor that terminates when we find the goal vertex
struct astar_goal_visitor:public boost::default_astar_visitor
{
astar_goal_visitor(vertex_descriptor goal):m_goal(goal) {};
void examine_vertex(vertex_descriptor u, const filtered_grid&) {
if (u == m_goal)
throw found_goal();
}
private:
vertex_descriptor m_goal;
};
void find_shortest(int start, int end, std::vector<std::map<ulong,unlong>>&
graph)
{
std::set<unlong> searchedList;
std::priority_queue<std::pair<ulong, ulong>> frontierList;
frontierList.push(std::make_pair(0, start));
while(!frontierList.empty())
{
std::pair<ulong, ulong> next = frontierList.top();
frontierList.pop();
if (next.second == end) {
std::cout << "Min Cost: " << next.first << "\n";
return;
}
if (searchedList.find(next.second) != searchedList.end()) {
continue;
}
searchedList.insert(next.second);
for(std::map<ulong,unlong>::const_iterator loop = graph[next.second].begin(); loop != graph[next.second].end(); ++loop)
{
frontierList.push(std::make_pair(next.first + loop->second, loop->first));
}
}
boolgrid_graph::solve() {
// The predecessor map is a vertex-to-vertex mapping.
typedef boost::unordered_map<vertex_descriptor,
vertex_descriptor,
vertex_hash> pred_map;
pred_map predecessor;
boost::associative_property_map<pred_map> pred_pmap(predecessor);
// The distance map is a vertex-to-distance mapping.
typedef boost::unordered_map<vertex_descriptor,
distance,
vertex_hash> dist_map;
dist_map distance;
boost::associative_property_map<dist_map> dist_pmap(distance);
vertex_descriptor s = source();
vertex_descriptor g = goal();
euclidean_heuristic heuristic(g);
astar_goal_visitor visitor(g);
try {
astar_search(m_barrier_grid, s, heuristic,
boost::weight_map(weight).
predecessor_map(pred_pmap).
distance_map(dist_pmap).
visitor(visitor) );
} catch(found_goal fg) {
// Walk backwards from the goal through the predecessor chain adding
// vertices to the solution path.
for (vertex_descriptor u = g; u != s; u = predecessor[u])
m_solution.insert(u);
m_solution.insert(s);
m_solution_length = distance[g];
return true;
}
return false;
}
int main(int,char*[]){
boost::array<std::size_t, 2> lengths = { { 30,50 } };
GraphType grid(lengths);
if (grid_graph.solve())
output << std::endl << "Solution length " << m.m_solution_length;
}
A brief background:
I'm building a semantic graph, using BGL directed graphs with an adjacency list:
class SemanticGraph
{
public:
typedef std::shared_ptr< Node > Node_ptr;
typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::directedS, Node_ptr > Graph;
typedef boost::graph_traits< Graph >::vertex_descriptor Vertex;
typedef boost::graph_traits< Graph >::edge_descriptor Edge;
One of the things I need to be able to do, is process a smaller graph (a subgraph) into my main graph.
Doing so, involves copying the Node pointers if needed, copying the vertices from the subgraph into the main graph if they don't already exist, and most importantly, copying the edges for each vertex found in the subgraph, into the main one, if not already established.
The first two tasks are not that complicated.
However, I cannot for the life of me, find a way to compare two edges:
void AddSubGraph( SemanticGraph subgraph )
{
typename boost::graph_traits<Graph>::vertex_iterator it, end;
Vertex vertex;
for ( auto node : subgraph._nodes )
{
if ( !findNode( node ) )
_nodes.push_back( node );
boost::tie( it, end ) = boost::vertices( _graph );
std::find_if( it, end, [&]( const Vertex vertex ){ return _graph[*it]->Hash() == node->Hash(); });
if ( it == end )
vertex = boost::add_vertex( node, _graph );
else
vertex = boost::vertex( *it, _graph );
boost::tie( it, end ) = boost::vertices( subgraph._graph );
std::find_if( it, end, [&]( const Vertex vertex ){ return subgraph._graph[*it]->Hash() == node->Hash(); });
auto subgraph_vertex = boost::vertex( *it, subgraph._graph );
typename boost::graph_traits<Graph>::out_edge_iterator a, z;
// Iterate subgraph's vertex out edges
for ( boost::tie ( a, z ) = boost::out_edges( subgraph_vertex, subgraph._graph );
a != z;
++a )
{
typename boost::graph_traits<Graph>::out_edge_iterator my_edge, edge_end;
boost::tie ( my_edge, edge_end ) = boost::out_edges( vertex, _graph );
// How can I see if the same edge as the one pointed by edge iterator a, exists in my vertex's edges?
std::find_if( my_edge, edge_end, [&]( const Edge edge ){ return edge == a; });
}
}
}
The compiler throws a warning at the last std::find_if above ^^:
‘const Edge {aka const boost::detail::edge_desc_impl<boost::directed_tag, long unsigned int>}’ is not derived from ‘const std::pair<_T1, _T2>’
Hinting that my lambda captured parameter should be a pair (I'm guessing an actual edge?).
So, my question is:
How can I find if that similar edge exists in my vertex's out edges?
you declared a as an iterator:
typename boost::graph_traits<Graph>::out_edge_iterator a, z;
It doesn't make sense to compare an iterator to an edge.
Instead, dereference the iterator to get the edge it is "pointing to":
std::find_if(my_edge, edge_end,
[&](const Edge edge) { return edge == *a; });
This compiles for me: See it Live on Coliru
Other problems:
std::find_if(it, end, [&](const Vertex vertex) { return _graph[*it]->Hash() == node->Hash(); });
statement has no effect (the result of the find is unused)
you are looking things up by a hash?! This is almost certainly a design error. If Hash() returns a hash, you CANNOT use it to identify nodes. Instead hash-tables use it to identify the the bucket in which to sort the node. However, to identify a node, you need to do an equality test (different Nodes can share the same hash)
the lambdas take their arguments by value. This is inefficient
Typical code would see
vertex_iterator match = std::find_if(it, end,
[&](Vertex const& vertex) { return _graph[*it] == node; });`
if (end != match)
{
// yes `match` is a match
}
#include <boost/graph/adjacency_list.hpp>
#include <memory>
struct Node
{
int id;
size_t Hash() const { return id; }
bool operator<(const Node& other) const { return id < other.id; }
bool operator==(const Node& other) const { return id==other.id; }
};
class SemanticGraph
{
public:
typedef std::shared_ptr< Node > Node_ptr;
typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::directedS, Node_ptr > Graph;
typedef boost::graph_traits< Graph >::vertex_descriptor Vertex;
typedef boost::graph_traits< Graph >::edge_descriptor Edge;
std::vector<Node_ptr> _nodes;
Graph _graph;
bool findNode(Node_ptr const& n) const { return std::find(begin(_nodes), end(_nodes), n) != end(_nodes); }
void AddSubGraph(SemanticGraph subgraph)
{
typename boost::graph_traits<Graph>::vertex_iterator it, end;
Vertex vertex;
for(auto node : subgraph._nodes)
{
if(!findNode(node))
{
_nodes.push_back(node);
}
boost::tie(it, end) = boost::vertices(_graph);
std::find_if(it, end, [&](const Vertex vertex) { return _graph[*it]->Hash() == node->Hash(); });
if(it == end)
vertex = boost::add_vertex(node, _graph);
else
vertex = boost::vertex(*it, _graph);
boost::tie(it, end) = boost::vertices(subgraph._graph);
std::find_if(it, end, [&](const Vertex vertex) { return subgraph._graph[*it]->Hash() == node->Hash(); });
auto subgraph_vertex = boost::vertex(*it, subgraph._graph);
typename boost::graph_traits<Graph>::out_edge_iterator a, z;
// Iterate subgraph's vertex out edges
for(boost::tie(a, z) = boost::out_edges(subgraph_vertex, subgraph._graph);
a != z;
++a)
{
typename boost::graph_traits<Graph>::out_edge_iterator my_edge, edge_end;
boost::tie(my_edge, edge_end) = boost::out_edges(vertex, _graph);
// How can I see if the same edge as the one pointed by edge iterator a, exists in my vertex's edges?
std::find_if(my_edge, edge_end, [&](const Edge edge) { return edge == *a; });
}
}
}
};
int main()
{
SemanticGraph g;
}