BGL edge bundled properties - boost-graph

I am using bundled properties, like this
class cVertex { ... };
class eEdge { ... };
typedef boost::adjacency_list <
boost::vecS, boost::vecS, boost::undirectedS,
cVertex, cEdge >
graph_t;
graph_t myGraph;
This works nicely for the vertices. I can write code to access the vertex bundled properties easily
const cVertex& v = myGraph[ *vertices(myGraph).first + idx ];
However, the same thing does not seem to work for edges
const cEdge& e = myGraph[ *edges(myGraph).first + idx ];
I get these compiler errors
1>.\cGraph.cpp(109) : error C2678: binary '+' :
no operator found which takes a left-hand operand of type
'boost::detail::edge_desc_impl<Directed,Vertex>'
(or there is no acceptable conversion)
I have also tried this:
For vertices this works well
boost::graph_traits<graph_t>::vertex_iterator vi = vertices(myGraph).first;
vi += idx;
But this gives compiler errors
boost::graph_traits<graph_t>::edge_iterator ei = edges(myGraph).first;
ei += idx;
Here is the error
>C:\boost\boost_1_51\boost/iterator/iterator_adaptor.hpp(330) :
error C3767: '+=': candidate function(s) not accessible
1> could be the friend function at 'C:\boost\boost_1_51\boost/graph/topology.hpp(63)' :
'+=' [may be found via argument-dependent lookup]

The adjacency_list does not contain a single vector of edges for the graph, but stores the edges as an... adjacency lists. This means that each vertex stores its own edges as a list of adjacent vertices.
There are in boost many other data structures to represent a graph, e.g. the edge_list which will make your edges directly accessible, the adjacency_matrix or the compressed sparse row graph (for fast and compact read only access).
You should also be able to solve your problem by creating a custom (property) map to directly access your edges.

I have found this workaround
boost::graph_traits<graph_t>::edge_iterator ei = edges(myGraph).first;
for( int k = 0; k < idx; k++ ) {
ei++;
}
It seems incredible that this is necessary!!!
As suggested by Jeremiah Willcock this code can be made to look simpler by writing
boost::graph_traits<graph_t>::edge_iterator ei = edges(myGraph).first;
std::advance( ei, idx );
( In reality the code executed is the same, with the addition of a test on whether or not the iterator is random access and the function call itself )

Related

How can I use `ListS` instead of `VecS` as underlying container and be able to do the same thing?

I usually work with vecS as container for boost::adjacency_list:
struct myVertexType { std::vector<Stuff> vec; /* and more */ };
struct myEdgeType { /* some data too */ };
using Graph = boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::directedS,
myVertexType,
myEdgeType
>;
However, I encountered a situation were that raised an issue:
I was referencing some data stored as a bundled property of a vertex and when I created another vertex, that seemed to make my reference invalid (1).
At least that's what I understood from reading this page (section "Iterator and Descriptor Stability/Invalidation").
So I switched to listS, and all went fine:
using Graph = boost::adjacency_list<
boost::listS,
boost::listS,
boost::directedS,
myVertexType,
myEdgeType
>;
Until...
Until I noticed that with listS, boost::target( e1, g ) fails to compile! :
Graph g;
auto e1 = boost::add_edge(1, 0, g).first;
auto t = boost::target( e1, g );
This fails to build too: (see on coliru)
Graph g;
boost::add_edge(1, 0, g);
write_graphviz(std::cout, g );
So I searched a bit and found an answer by Sehe, stating that
vecS has an implicit vertex index.
listS doesn't. Therefore it uses the internal property vertex_index_t
However, the given answer uses Inner properties (?) (or is it dynamic properties?) and I am using my own datatypes for vertices and edges.
So my question is:
How can I build a list-based graph type that enables me to do all the "regular stuff" allowed by VecS?
(1) to be clear, I was referencing a vector that was in a vertex, and when I created another vertex, the vector suddenly became empty!
Edit: clarified what is inside my nodes.
Background
"when I created another vertex, that seemed to make my reference invalid (1)."
Yes, that's possible.
You have to realize that there's are much bigger performance trade-offs underlying your choice of container selectors. Many algorithms can get very different efficiency characteristics.
Also, some semantics subtly change (e.g. when using setS as the edge container selector, you naturally cannot have duplicate edges anymore; this is also why add_edge returns a pair<descriptor, bool>).
Also realize that often you don't need reference or even iterator stability. The typical coding pattern in BGL is not to pass/hold references to property (bundles), but instead pass property maps by value.
Property maps abstract aways access to (mutable) properties.
You can usually pass descriptors which usually are stable (unless you're removing vertices "in the middle" in vecS, as the implied vertex index is obviously changing for all following vertices).
That said, let's move on to your problems:
Questions
Until I noticed that with listS, boost::target( e1, g ) fails to compile!
Nope. That compiles fine.
What ISN'T fine is that you call add_edge with integral arguments. The vertex descriptor isn't integral with lists/setS (node based containers).
Worse, vertices don't get automatically added for non-vecS adjacency_list so you'd be referring to vertices out-of-range anyways.
The general way to refer to these is:
V v0 = add_vertex(g);
V v1 = add_vertex(g);
auto [e1, inserted] = boost::add_edge(v0, v1, g);
assert(inserted);
[[maybe_unused]] V t = boost::target(e1, g);
The graphviz call is also fine, but fails for the same reason on add_edge...
Also, you need to add a vertex index. Either as interior property or passing a property map to the algorithm function.
Here's a complete test demo that shows all three flavours:
Live On Coliru
#include <boost/algorithm/string.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/core/demangle.hpp>
#include <iostream>
#include <numeric>
using boost::core::demangle;
using boost::algorithm::replace_all_copy;
struct myVertexType { /* some data */ };
struct myEdgeType { /* some data too */ };
template <typename containerS> void tests() {
using Graph = boost::adjacency_list<
containerS, containerS,
boost::directedS,
myVertexType,
myEdgeType>;
using V = typename Graph::vertex_descriptor;
std::cout << "\n"
<< std::boolalpha << "tests() with "
<< demangle(typeid(containerS).name()) << " - "
<< "vertex_descriptor integral? " << std::is_integral<V>()
<< "\n";
Graph g;
V v0 = add_vertex(g);
V v1 = add_vertex(g);
auto [e1, inserted] = boost::add_edge(v0, v1, g);
assert(inserted);
[[maybe_unused]] V t = boost::target(e1, g);
std::ostringstream dot;
if constexpr (std::is_same<boost::vecS, containerS>()) {
boost::write_graphviz(dot, g);
} else {
std::map<V, int> index;
for (auto v : boost::make_iterator_range(vertices(g)))
index.emplace(v, index.size());
auto index_map = boost::make_assoc_property_map(index);
boost::dynamic_properties dp;
dp.property("node_id", index_map); // get(boost::vertex_index, g)
boost::write_graphviz_dp(dot, g, dp);
}
std::cout << "dot: " << replace_all_copy(dot.str(), "\n", "") << "\n";
}
int main() {
tests<boost::vecS>();
tests<boost::setS>();
tests<boost::listS>();
}
Prints
tests() with boost::vecS - vertex_descriptor integral? true
dot: digraph G {0;1;0->1 ;}
tests() with boost::setS - vertex_descriptor integral? false
dot: digraph G {0;1;0->1 ;}
tests() with boost::listS - vertex_descriptor integral? false
dot: digraph G {0;1;0->1 ;}

boost graph copy a filtered_graph

My actual intention is to use mcgregor_common_subgraphs to find some induced partial subgraphs. But it takes too long to compare small graphs. So instead of comparing the entire graphs I want to filter out a subset of comparable vertices and edges. Then find subgraphs between them.
So I use filtered_graph with vertex and edge predicates for both of these graphs. And pass the filtered graph to mcgregor_common_subgraphs. But it complains
error: use of deleted function ‘boost::iterators::filter_iterator<bya::util::isomorphism::directed_graph::vertex_filter_predicate, boost::range_detail::integer_iterator<long unsigned int> >& boost::iterators::filter_iterator<bya::util::isomorphism::directed_graph::vertex_filter_predicate, boost::range_detail::integer_iterator<long unsigned int> >::operator=(const boost::iterators::filter_iterator<bya::util::isomorphism::directed_graph::vertex_filter_predicate, boost::range_detail::integer_iterator<long unsigned int> >&)’
So I planned to copy the filtered graph into a new graph with copy_graph. But it complains that there is no default constructor for the vertex predicate vertex_filter_predicate.
error: no matching function for call to ‘bya::util::isomorphism::directed_graph::vertex_filter_predicate::vertex_filter_predicate()’
However my vertex and edge predicate takes a const reference to the original graph. So I cannot add an empty default constructor. I have searched in boost documentation, but didn't find any example of copying a filtered_graph. What is the solution ? Also why would copy_graph require the predicates to copy ?
struct directed_graph{
typedef boost::adjacency_list<boost::setS, boost::vecS, boost::bidirectionalS, vertex_data, edge_data> graph_type;
graph_type _graph;
// ...
};
I have a directed graph struct inside which I have the predicates and equivalance comparators. Both small and large are instances of directed_graph.
directed_graph::edge_filter_predicate edge_filter_small (small._graph, allowed_vertex_ids), edge_filter_large (large._graph, allowed_vertex_ids);
directed_graph::vertex_filter_predicate vertex_filter_small(small._graph, allowed_vertex_ids), vertex_filter_large(large._graph, allowed_vertex_ids);
boost::filtered_graph<directed_graph::graph_type, directed_graph::edge_filter_predicate, directed_graph::vertex_filter_predicate> filtered_small_view(small._graph, edge_filter_small, vertex_filter_small);
boost::filtered_graph<directed_graph::graph_type, directed_graph::edge_filter_predicate, directed_graph::vertex_filter_predicate> filtered_large_view(large._graph, edge_filter_large, vertex_filter_large);
directed_graph::graph_type filtered_small;
boost::copy_graph(filtered_small_view, filtered_small);

Boost filtered graph with blacklisted edges

I want to run Dijkstra on a graph with blacklisted edges, i.e., I want to compute shortest paths that do not use those links.
For the moment, I first define the filter:
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;
struct BlackListEdgeConstraint
{
private:
std::set blackList;
graph_t* g;
public:
BlackListEdgeConstraint():blackList(std::set<Edge>() ),g(NULL){};
BlackListEdgeConstraint(std::set<Edge>& list, graph_t* g_) : blackList(list), g(g_)
{
}
/**
* 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) );
//Include the edge if it's not in the blacklist.
return blackList.find( edge ) == blackList.end();
}
};
and then I do this in may main(...) function
... I fill the graph g ...
std::set<Edge> blacklist; blacklist.insert( Edge(0,1) );
BlackListEdgeConstraint filter(blacklist, &g);
boost::filtered_graph<graph_t, BlackListEdgeConstraint> filtered(g, filter);
... I run Dikjstra on the filtered graph ...
Now, what I did works, but it is weird. Indeed, I first create an edge between vertex 0 and 1. Then, inside the operator() (...), I have an edge_descriptor instead of an Edge (if I put Edge as parameter, the compiler complains as explained here, so I guess boost is performing some conversion somewhere and for a reason I do not know). Then, I retrieve again the vertices 0 and 1 inside operator() (...) and I reconstruct the Edge. You understand that I am taking a long tour to do something that would be easy if only the operator()(..) accepted directly Edge.
Do you think I can do the same operations in a more elegant and efficient way?
What you basically ask has little to do with boost graph. You want to be able to lookup a pair of vertex-descriptors, efficiently.
The deciding factor will be the choice of data structure for the blacklist.
As a side note, you do not insert Edge objects into the graph. The graph model you chose is an adjacency-list so it stores lists of adjacent vertices for each vertex.
The pair<int,int> is merely a convenience type that is used for easily initializing the graph. You could do without it entirely.
After thinking about the possible choices I don't think there's a swifter way.
At certain, large, scales, you might get higher effective performance
using a blacklist that is itself represented as an adjacency-list (e.g. std::map<source_vertex, std::list<target_vertex> >) or
to use an unordered_set<>.
Both optimizations are unlikely to yield significant difference unless at large scale.
you might benefit from locality-of-reference by using a boost::container::flat_set
Is The Cost Real?
If you think that "constructing the Edge" is a waste of resources, forget about that: it's a POD type (std::pair<int, int>) and as such has only trivial constructor/destructors. Seeing that set<Edge> is a template class, most operations on it can be inlined. The compiler will inline away the method call, enregister the arguments, remove redundant load/store cycles and effectively generate optimal code:
#include <set>
#include <utility>
using Edge = std::pair<int, int>;
using Blacklist = std::set<Edge>;
struct Pred {
Blacklist blacklist { {
{ 92, 29 },
{ 74, 92 },
{ 86, 6 },
{ 67, 35 },
{ 59, 4 },
{ 66, 13 },
{ 82, 37 },
{ 51, 94 },
{ 32, 6 }
} };
bool operator()(int source, int target) const {
return blacklist.end() != blacklist.find(Edge {source, target});
}
};
View Disassembly Live On Godbolt GCC Explorer
Pro tip: Click # button on the Clang disassembly to see optimizer comments
Summary The cost of the Edge type is non-existent. Any real cost would be down to using boost::source() and boost::target() on the edge_descriptor.
Seeing that your edge container selector is listS, your edge containers are node-based, meaning that the edge-descriptor is stable, and basically a type-erased reference to the edge object. Calling boost::source(e, g) does little more than casting the descriptor and dereferencing. Depending on how it's used, the compiler might even be able to see through these indirections.
If that cost is not to your liking, tweak the Graph type :) (Your use case might warrant an EdgeList concept instead, or benefit from using a node-based edge container etc.).

When analyzing boost::graph, does one operate on vertex and edge descriptor or their iterators?

When working with the BOOST graph library, I have a graph instance fully initialized -- the structure is now static. I need to do some processing based on the graph.
I'm not clear if I should be working with the iterator types for vertices and edges, or the vertex and edge types themselves?
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, VertexProperty, EdgeProperty > GraphType;
typedef typename boost::graph_traits< GraphType >::vertex_descriptor VertexType;
typedef typename boost::graph_traits<GraphType>::vertex_iterator VertexIterator;
typedef typename boost::graph_traits< GraphType >::edge_descriptor EdgeType;
typedef typename boost::graph_traits<GraphType>::out_edge_iterator EdgeIterator;
I have an algorithm where I need to check if two edges are "the same". (in the strongest sense. Suppose the graph has two parallel edges connecting E1(S1,T2) & E2(S1,T2). An edge can only be "the same" with at most one of them.
What's the difference between (edge_descriptor == edge_descriptor) and (edge_iterator == edge_iterator)? Same question for vertices.
Most of the graph functions return iterators rather than the edge/vertex types themselves.
I also have the need to store a set of edges. Not sure whether I should be storing EdgeType or EdgeIterator?
std::vector<EdgeType> processedEdges;
std::vector<EdgeIterator> processedEdges;
vit = std::find( processedEdges.begin(), processedEdges.end(), anotherEdgeRef )
if ( vit == processedEdges.end() )
doSomethingBasedOnEdgeProperty(*vit);
Reference:
http://www.boost.org/doc/libs/1_64_0/libs/graph/doc/adjacency_list.html
You should be storing descriptors, not iterators.
Iterators relate to a logical range, not the graph. Iterators may not be valid between different ranges of the same graph:
auto range1 = out_edges(vertex1, g);
auto range2 = out_edges(vertex2, g);
assert(range1.first != range2.first); // unspecified or undefined
Instead, descriptors are graph-wide. Depending on graph model, descriptors may be more stable: if an operation invalidates iterators, it doesn't necessarily invalidate the descriptors corresponding to the same graph elements.
In other words, this makes descriptors more usable as vertex or edge "ID" - or, as Boost Graph would call it, vertex_index or edge_index properties.
I think that is very close to your question.
One caveat: even so, descriptors may not always be stable!
E.g.:
adjacency_list<vecS, vecS, directedS>
leads to vertex descriptors that are stable on append, but not on
deletion.
adjacency_list<setS, listS, directedS>
on the other hand, leads to vertex descriptors that are stable on both
insertion and removal.
See documentation section "Iterator and Descriptor
Stability/Invalidation"
If you need a completely stable identity for your graph elements, you may need to add one as a (bundled) property.

Boost graph library: Get edge_descriptor or access edge by index of type int

I'm a BGL newbie with a (possibly) easy question: I have a directed graph and use bundled properties for edges, one of them being an index of type int. Knowing a unique index, I would like to get the corresponding edge_descriptor of that edge in order to perform operations on it. The following example summarizes my problem:
#include <boost/graph/adjacency_list.hpp>
struct EdgeProperties {
EdgeProperties(): distance(10), time_limit(5) {};
int index;
int distance;
int time_limit;
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, boost::no_property, EdgeProperties> Graph;
int main() {
Graph graph;
EdgeProperties edge_prop1, edge_prop2, edge_prop3, edge_prop4;
// Define edge properties
edge_prop1.index = 0;
edge_prop2.index = 1;
edge_prop3.index = 2;
edge_prop4.index = 3;
// Add edges to graph
boost::add_edge(0, 1, edge_prop1, graph);
boost::add_edge(0, 2, edge_prop2, graph);
boost::add_edge(1, 3, edge_prop3, graph);
boost::add_edge(2, 3, edge_prop4, graph);
// Get vertex_descriptor from an (int) index:
int vertex_index = 2;
boost::graph_traits<Graph>::vertex_descriptor v = boost::vertex(vertex_index, graph);
// I would like to get an edge_descriptor from an (int) index property:
// The following DOES NOT work:
boost::graph_traits<Graph>::edge_descriptor e = boost::edge(edge_prop1.index, graph);
}
I read about property maps as well, but could not find a solution my problem. I would prefer bundled properties over internal properties.
Is there a way of assigning unique int type indices via a bundle property to edges and access edges through these int type values?
Sadly, I don't think boost::graph is of immediate help here.
First, there is no mechanism to find an edge (or vertex, for that matter), based on a field of an edge property - BGL keeps any such mapping, and the 'index' field you have is entirely for your purposes.
Second, there is the boost::edges function that returns an iterator range for all edges of the graph. I though that you could pass vecS as edge container type to adjacency_list template, and then look inside this range, but per http://www.boost.org/doc/libs/1_61_0/libs/graph/doc/EdgeListGraph.html the iterators are only required to be multi-pass input iterators, and the implementation does exactly that -- even with vecS as edge type, you can't do random access.
Therefore, it seems that the only way to accomplish what you want is to keep your own unodered_map from index to edge descriptor.