I'm trying to use the dijkstra shortest path algorithm in BGL to compute a simple ST path on an unweighted undirected graph. I may care about edge weights in the future, but for now I just want to consider edge traversals to be a uniform cost.
I am also tracking multiple edge and vertex properties so I've based what I've done so far on the bundled properties example that seemed to be the closest to what I'm attempting to do.
Now I'm trying to figure out how to get dijkstra working so I can do my ST search but I am getting stuck on getting the right parameters set up for it.
Here's a simplified example of the code I have so far:
#include <iostream>
#include <vector>
#include <boost/config.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>
// Create a struct to hold properties for each vertex
typedef struct VertexProperties
{
int p1;
} VertexProperties;
// Create a struct to hold properties for each edge
typedef struct EdgeProperties
{
int p1;
} EdgeProperties;
// Define the type of the graph
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexProperties, EdgeProperties> Graph;
int main(int,char*[])
{
// Create a graph object
Graph g;
// Add vertices
Graph::vertex_descriptor v0 = boost::add_vertex(g);
Graph::vertex_descriptor v1 = boost::add_vertex(g);
Graph::vertex_descriptor v2 = boost::add_vertex(g);
// Set vertex properties
g[v0].p1 = 1;
g[v1].p1 = 2;
g[v2].p1 = 3;
// Add edges
std::pair<Graph::edge_descriptor, bool> e01 = boost::add_edge(v0, v1, g);
std::pair<Graph::edge_descriptor, bool> e02 = boost::add_edge(v1, v2, g);
// Set edge properties
g[e01.first].p1 = 1;
g[e02.first].p1 = 2;
std::cout << "num_verts: " << boost::num_vertices(g) << std::endl;
std::cout << "num_edges: " << boost::num_edges(g) << std::endl;
// compute ST shortest paths here...
return 0;
}
I'm getting tripped up on the right parameters for the call to dijkstra's algorithm. They take the graph, a starting vertex, and then a predecessor map and distance map. The examples I've seen so far, like this one set up their graph with just an edge weight without the bundled edge properties, which simplifies things.
Ultimately, I'm after the ST shortest path so I'd need to recover the path from S to T. From the looks of things, we need to set up a predecessor map and then we can use that to extract the path from a particular T back to S?
I should also note that the environment I'm in does not allow C++11 language features. :(
Any help here would be greatly appreciated!
So the question was "how to use a bundled property as weight map with Boost Graph Library?".
Good. You use property maps. The bundled property can be accessed with a little bit of funky syntax documented right on the "Bundled Properties" page: http://www.boost.org/doc/libs/1_58_0/libs/graph/doc/bundles.html, see heading "Property maps from bundled properties".
Now for a quick demo:
// set up a weight map:
auto weights = boost::get(&EdgeProperties::p1, g);
Passing the minimum amount of arguments to dijkstra:
// you can pass it to dijkstra using direct or named params. Let's do the simplest
boost::dijkstra_shortest_paths(g, v0, boost::no_named_parameters() .weight_map(weights));
You will want to add more parameters, but hey, this is your start :)
Live On Coliru
#include <iostream>
#include <vector>
#include <boost/config.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/graph_utility.hpp>
// Create a struct to hold properties for each vertex
struct VertexProperties { int p1; };
// Create a struct to hold properties for each edge
struct EdgeProperties { int p1; };
// Define the type of the graph
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexProperties, EdgeProperties> Graph;
int main() {
// Create a graph object
Graph g;
// Add vertices
auto v0 = boost::add_vertex({1}, g),
v1 = boost::add_vertex({2}, g),
v2 = boost::add_vertex({3}, g);
// Add edges
boost::add_edge(v0, v1, EdgeProperties{1}, g);
boost::add_edge(v1, v2, EdgeProperties{2}, g);
boost::print_graph(g, boost::get(&VertexProperties::p1, g));
// set up a weight map:
auto weights = boost::get(&EdgeProperties::p1, g);
// you can pass itprint_graph`enter code here` to dijkstra using direct or named params. Let's do the simplest
boost::dijkstra_shortest_paths(g, v0, boost::no_named_parameters() .weight_map(weights));
}
You'll note that I simplified the initialization of the vertex/edge properties as well. The print_graph utility is neat if you want to have an idea of what the graph "looks" like (short of using Graphviz).
The output on Coliru is:
1 <--> 2
2 <--> 1 3
3 <--> 2
I'm adding a 'finished' version of the dijkstra shortest paths search that computes the shortest path from S to T for archival purposes.
I'm sure there are better "boost" ways to do this, but it works on my end.
http://www.boost.org/doc/libs/1_58_0/libs/graph/doc/bundles.html was a really helpful link.
///
/// #file bgl_st_example.cpp
///
/// #brief bundled property example
///
/// #ref http://programmingexamples.net/wiki/CPP/Boost/BGL/BundledProperties
///
#include <iostream>
#include <vector>
#include <boost/config.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/graph_utility.hpp>
// Create a struct to hold properties for each vertex
typedef struct vertex_properties
{
std::string label;
int p1;
} vertex_properties_t;
// Create a struct to hold properties for each edge
typedef struct edge_properties
{
std::string label;
int p1;
int weight;
} edge_properties_t;
// Define the type of the graph
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, vertex_properties_t, edge_properties_t> graph_t;
typedef graph_t::vertex_descriptor vertex_descriptor_t;
typedef graph_t::edge_descriptor edge_descriptor_t;
typedef boost::property_map<graph_t, boost::vertex_index_t>::type index_map_t;
typedef boost::iterator_property_map<vertex_descriptor_t*, index_map_t*, vertex_descriptor_t, vertex_descriptor_t&> predecessor_map_t;
// The graph, with edge weights labeled.
//
// v1 --(1)-- v2
// | \_ |
// | \ |
// (1) (3) (2)
// | \_ |
// | \ |
// v4 --(1)-- v3
//
//
int main(int,char*[])
{
// Create a graph object
graph_t g;
// Add vertices
vertex_descriptor_t v1 = boost::add_vertex(g);
vertex_descriptor_t v2 = boost::add_vertex(g);
vertex_descriptor_t v3 = boost::add_vertex(g);
vertex_descriptor_t v4 = boost::add_vertex(g);
// Set vertex properties
g[v1].p1 = 1; g[v1].label = "v1";
g[v2].p1 = 2; g[v2].label = "v2";
g[v3].p1 = 3; g[v3].label = "v3";
g[v4].p1 = 4; g[v4].label = "v4";
// Add edges
std::pair<edge_descriptor_t, bool> e01 = boost::add_edge(v1, v2, g);
std::pair<edge_descriptor_t, bool> e02 = boost::add_edge(v2, v3, g);
std::pair<edge_descriptor_t, bool> e03 = boost::add_edge(v3, v4, g);
std::pair<edge_descriptor_t, bool> e04 = boost::add_edge(v4, v1, g);
std::pair<edge_descriptor_t, bool> e05 = boost::add_edge(v1, v3, g);
// Set edge properties
g[e01.first].p1 = 1; g[e01.first].weight = 1; g[e01.first].label = "v1-v2";
g[e02.first].p1 = 2; g[e02.first].weight = 2; g[e02.first].label = "v2-v3";
g[e03.first].p1 = 3; g[e03.first].weight = 1; g[e03.first].label = "v3-v4";
g[e04.first].p1 = 4; g[e04.first].weight = 1; g[e04.first].label = "v4-v1";
g[e05.first].p1 = 5; g[e05.first].weight = 3; g[e05.first].label = "v1-v3";
// Print out some useful information
std::cout << "Graph:" << std::endl;
boost::print_graph(g, boost::get(&vertex_properties_t::label,g));
std::cout << "num_verts: " << boost::num_vertices(g) << std::endl;
std::cout << "num_edges: " << boost::num_edges(g) << std::endl;
// BGL Dijkstra's Shortest Paths here...
std::vector<int> distances( boost::num_vertices(g));
std::vector<vertex_descriptor_t> predecessors(boost::num_vertices(g));
boost::dijkstra_shortest_paths(g, v1,
boost::weight_map(boost::get(&edge_properties_t::weight,g))
.distance_map(boost::make_iterator_property_map(distances.begin(), boost::get(boost::vertex_index,g)))
.predecessor_map(boost::make_iterator_property_map(predecessors.begin(), boost::get(boost::vertex_index,g)))
);
// Extract the shortest path from v1 to v3.
typedef std::vector<edge_descriptor_t> path_t;
path_t path;
vertex_descriptor_t v = v3;
for(vertex_descriptor_t u = predecessors[v]; u != v; v=u, u=predecessors[v])
{
std::pair<edge_descriptor_t,bool> edge_pair = boost::edge(u,v,g);
path.push_back( edge_pair.first );
}
std::cout << std::endl;
std::cout << "Shortest Path from v1 to v3:" << std::endl;
for(path_t::reverse_iterator riter = path.rbegin(); riter != path.rend(); ++riter)
{
vertex_descriptor_t u_tmp = boost::source(*riter, g);
vertex_descriptor_t v_tmp = boost::target(*riter, g);
edge_descriptor_t e_tmp = boost::edge(u_tmp, v_tmp, g).first;
std::cout << " " << g[u_tmp].label << " -> " << g[v_tmp].label << " (weight: " << g[e_tmp].weight << ")" << std::endl;
}
return 0;
}
Here's a CMakeLists.txt file that works for me:
cmake_minimum_required(VERSION 2.8)
project ( bgl_example )
find_package( Boost REQUIRED COMPONENTS )
include_directories( ${Boost_INCLUDE_DIR} )
add_executable( bgl_st_example bgl_st_example.cpp)
target_link_libraries( bgl_st_example ${Boost_LIBRARIES} )
The resulting output that I see:
Graph:
v1 <--> v2 v4 v3
v2 <--> v1 v3
v3 <--> v2 v4 v1
v4 <--> v3 v1
num_verts: 4
num_edges: 5
Shortest Path from v1 to v3:
v1 -> v4 (weight: 1)
v4 -> v3 (weight: 1)
Related
#include <boost/graph/adjacency_list.hpp>
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
boost::no_property,
boost::property<boost::edge_weight_t, double>>
DiGraph;
typedef boost::graph_traits<DiGraph>::vertex_descriptor vertex_descriptor;
int main () {
std::vector<std::size_t> vertices = { 1, 5, 10};
std::vector<std::pair<std::size_t, std::size_t>> edges = {std::make_pair(1, 5),
std::make_pair(5, 10)};
std::vector<double> weights = {2., 2.};
DiGraph di_graph (edges.begin(), edges.end(), weights.begin(), vertices.size());
DiGraph::vertex_descriptor v_start = boost::vertex(1, di_graph);
std::vector<vertex_descriptor> parents(boost::num_vertices(di_graph));
boost::dijkstra_shortest_paths(di_graph, v_start,
boost::predecessor_map(boost::make_iterator_property_map(parents.begin(), boost::get(boost::vertex_index, di_graph))));
}
This allocates a vector parents of size 11, since boost uses contiguous vertex indices.
I want the non-contiguous vertices (1, 5, 10..) but don't want the unnecessary memory space for the vector parents.
How can I make a mapping from my vertex indices to the vertex indices 1, 2, 3 and pass it to boost::dijkstra_shortest_paths?
On top of that it would be even more convenient to receive the result of dijkstra in a struct parents and access the predecessor of an element with my index, e.g.
parents[10]
but without a vector of length 11 or just have an easy conversion function f I could use
parents[f(10)]
I did take a look at the documentation of boost graph and thought the IndexMap could make this possible, but I don't understand the concept and can't make it work.
With the boost::vecS vertex container selection, the vertex index is implicit, and the call
DiGraph di_graph(
edges.begin(), edges.end(), weights.begin(), vertices.size());
is a lie: you tell it that there are 3 vertices, but then you index out of bounds (5, 10 are outside [0,1,2]).
Note also that
V v_start = boost::vertex(1, di_graph);
selects the second vertex, not vertex 1.
Internal Names
I'd probably suggest a more recent addition: internal vertex names. If we add a vertex property bundle, like simply int:
using DiGraph = boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::directedS,
int,
boost::property<boost::edge_weight_t, double>>;
And then also teach BGL that we can use it as the vertex internal name:
template<> struct boost::graph::internal_vertex_name<int> {
struct type : std::identity {
using result_type = int;
};
};
Now creating the equivalent graph is simply:
DiGraph g;
add_edge(1, 5, 2., g);
add_edge(5, 10, 2., g);
That's all. You can see that it created vertices with implicit indices as the descriptors:
for (auto e : make_iterator_range(edges(g))) {
std::cout << "edge: " << e << "\n";
}
Prints:
edge: (0,2)
edge: (1,0)
To get the names, use property maps like so:
for (auto v : make_iterator_range(vertices(g))) {
std::cout << "vertex at index " << v << " named " << g[v] << "\n";
}
Printing
vertex at index 0 named 5
vertex at index 1 named 1
vertex at index 2 named 10
Using internal vertex names, you can query vertices by property bundles:
boost::optional<V> v_start = g.vertex_by_property(1);
Now, all I can suggest is using safe iterator maps:
dijkstra_shortest_paths(
g,
v_start.value(),
boost::predecessor_map(boost::make_safe_iterator_property_map(
parents.begin(), parents.size(), get(boost::vertex_index, g))));
for (size_t i = 0; i < parents.size(); ++i) {
std::cout << "Parent for '" << g[i] << "' is '" << g[parents[i]] << "'\n";
}
Live Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <iostream>
template<> struct boost::graph::internal_vertex_name<int> {
struct type : std::identity {
using result_type = int;
};
};
using DiGraph = boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::directedS,
int,
boost::property<boost::edge_weight_t, double>>;
using V = DiGraph::vertex_descriptor;
using boost::make_iterator_range;
int main()
{
DiGraph g;
add_edge(1, 5, 2., g);
add_edge(5, 10, 2., g);
for(auto e : make_iterator_range(edges(g)))
std::cout << "edge: " << e << "\n";
for(auto v : make_iterator_range(vertices(g)))
std::cout << "vertex at index " << v << " named " << g[v] << "\n";
boost::optional<V> v_start = g.vertex_by_property(1);
std::vector<V> parents(num_vertices(g));
dijkstra_shortest_paths(
g,
v_start.value(),
boost::predecessor_map(boost::make_safe_iterator_property_map(
parents.begin(), parents.size(), get(boost::vertex_index, g))));
for (size_t i = 0; i < parents.size(); ++i) {
std::cout << "Parent for '" << g[i] << "' is '" << g[parents[i]] << "'\n";
}
}
Prints
edge: (0,2)
edge: (1,0)
vertex at index 0 named 5
vertex at index 1 named 1
vertex at index 2 named 10
Parent for '5' is '1'
Parent for '1' is '1'
Parent for '10' is '5'
First step: take a look at bundled properties https://www.boost.org/doc/libs/1_79_0/libs/graph/doc/bundles.html
Second:
the non-contiguous vertices (1, 5, 10..) " these should be regarded as properties of the vertex. e.g "1" is a property of vertex 0.
Third: create a vertex class with 1, 5, 10.. as public attributes
Four: Create a boost graph using the your vertex class, setting and getting 1, 5, 10.. as described in the bundled properties page.
The simplest boost::adjacency_list uses std::size_t as the underlying vertex_descriptor (index).
boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::directedS,
boost::no_property,
boost::no_property
> graph;
Once I know the vertex descriptor, I can quickly access the desired vertex.
auto vertex = graph[idx]; // idx is the veretx descriptor
However, when the graph is mutated, there is no guarantee that the vertex_decriptor will be stable.
auto v0 = boost::add_vertex(graph);
auto v1 = boost::add_vertex(graph);
auto v2 = boost::add_vertex(graph);
boost::remove_vertex(v0, graph); // v1 and v2 are no longer valid
I would like to be able to find a specific vertex quickly - meaning I wish to avoid traversing the graph structure in search of a vertex I know exists.
My thinking is that I can somehow tweak boost::adjacency_list with a different selector to the VertexListS template parameter, that will allow me to use my own provided vertex_descripor (index).
I explored the possibility of using an associative container selector such as the builtin boost::hash_mapS but it seems I can't control the exact id it returns when calling add_vertex.
I would like to be able to use my own id (vertex_descriptor) for each vertex.
I'll try to be a bit more clear, with the code I wish would work:
// the following type will serve as the vertex_descriptor:
using my_id_type = std::size_t;
struct MyVertex
{
my_id_type id;
// some other fields
}
// add vertices with unique identifiers that will also serve as the vertex_descriptors
boost::add_vertex(MyVertex{1111}, graph); // I expect this to return 1111
boost::add_vertex(MyVertex{2222}, graph);
boost::add_vertex(MyVertex{1234}, graph);
boost::remove_vertex(1111, graph); // I expect this to remove the first vertex
// access a specific vertex using its unique, stable descriptor:
auto vertex = graph[1234];
Can this be achieved using boost::graph?
Can this be achieved using boost::graph?
The answer is nearly always "yes" with BGL. It's one of the most profoundly generic library designs in existence.
To my surprise, something new appeared in the type-hierarchy for adjacency_list. Apparently these days there's a named_graph mixin (actually maybe_name_graph) which uses traits on the vertex bundle to detect an "internal name".
This means you can get close. Though the vertex descriptor will not become your id, you can have O(1) lookup. And the interface has some conveniences, so you can write:
add_edge(1111, 2222, g);
add_edge(2222, 1111, g);
Notes:
the lookup is amortized O(1) because the name-vertex lookup is based on an unordered (hash) index
you run into problems (e.g. ambiguous overloads for add_edge) if you make the id type accidentally the same as the vertex_descriptor type (or if your argument have equally "far" conversions to either).
as far as I can tell, the internal name property is not automatically picked up as the vertex_index_t nor vertex_name_t property.
Step #1: The Graph
struct Vertex {
size_t id;
std::string other = "default-constructed";
};
using Graph =
boost::adjacency_list<boost::vecS, boost::listS, boost::directedS, Vertex>;
So far no surprises. I
opted to add a second member other just to show when/how it gets default constructed
opted listS because it
a. offers reference/descriptor stability (except for removed vertices)
b. leads to opaque vertex_descriptor (void*) which does not conflict with the id type (size_t) in overload resolution
Step #2: Name Traits
Next we teach BGL about our Internal Vertex Name.
This is purely parameterized by Vertex bundle, so keep in mind that different graphs using the same bundle would use the same name traits.
// traits
template <> struct boost::graph::internal_vertex_name<Vertex> {
struct type {
using result_type = size_t;
result_type const& operator()(Vertex const& bundle) const {
return bundle.id;
}
};
};
template <> struct boost::graph::internal_vertex_constructor<Vertex> {
struct type {
private:
using extract_name_type = typename internal_vertex_name<Vertex>::type;
using vertex_name_type = typename remove_cv<typename remove_reference<
typename extract_name_type::result_type>::type>::type;
public:
using argument_type = vertex_name_type;
using result_type = Vertex;
result_type operator()(const vertex_name_type& id) const {
return {id};
}
};
};
Note
We could of course hard-code the knowns in the second specialization:
template <> struct boost::graph::internal_vertex_constructor<Vertex> {
struct type {
Vertex operator()(size_t id) const { return {id}; }
};
};
It is very important to return the id by reference. Failing to do so leads to UB with no diagnostics from the library/compiler
Step #3 Adding/Finding Vertices
Now you can add vertices. Either by "name" (your id):
auto x = add_vertex(1111, g); // by id
Or the old-fashioned way you anticipated in the question:
add_vertex(Vertex{2222, "twotwotwotwo"}, g); // or with full bundle
Duplicate additions have no effect:
assert(add_vertex(1111, g) == x);
Different lookups exist. The vertex_by_property returns a optional<vertex_descriptor> given a vertex bundle.
assert(x == *g.vertex_by_property(g[x]));
It does so by extracting the "internal name" from the bundle passed, so there is no need for the bundle to contain any other state outside the id:
assert(x == *g.vertex_by_property(Vertex{1111}));
Although it feels like an implementation detail, the actual multi_index_container implementing the name -> descriptor index is also exposed:
assert(x == *g.named_vertices.find(1111));
Step #4 Adding Edges
add_edge(1111, 2222, g);
add_edge(2222, 1111, g);
That borrows a page from your previous question :)
Obviously, you can still add edges by vertex descriptors.
Step #5 Other Operations
Borrowing more pages from that previous answer:
print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
print_graph(g, get(&Vertex::other, g), std::cout << "---\n");
std::cout << "---\n";
for (auto *v : boost::make_iterator_range(vertices(g))) {
auto const& [id, other] = g[v];
std::cout << id << " " << std::quoted(other) << "\n";
}
if (auto v = g.vertex_by_property({1111})) {
std::cout << "==== removing " << g[*v].id << "\n";
clear_vertex(*v, g); // clear edges
remove_vertex(*v, g); // remove the vertex
}
print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
print_graph(g, get(&Vertex::other, g), std::cout << "---\n");
Full Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>
#include <iostream>
#include <iomanip>
struct Vertex {
size_t id;
std::string other = "default-constructed";
};
using Graph =
boost::adjacency_list<boost::vecS, boost::listS, boost::directedS, Vertex>;
// traits
template <> struct boost::graph::internal_vertex_name<Vertex> {
struct type {
using result_type = size_t;
result_type const& operator()(Vertex const& bundle) const {
return bundle.id;
}
};
};
template <> struct boost::graph::internal_vertex_constructor<Vertex> {
struct type {
private:
using extractor = typename internal_vertex_name<Vertex>::type;
using name_t = std::decay_t<typename extractor::result_type>;
public:
using argument_type = name_t;
using result_type = Vertex;
result_type operator()(const name_t& id) const { return {id}; }
};
};
int main() {
Graph g;
{
auto x = add_vertex(1111, g); // by id
add_vertex(Vertex{2222, "twotwotwotwo"}, g); // or with full bundle
// duplicate additions have no effect
assert(add_vertex(1111, g) == x);
// different lookups exist
assert(x == *g.named_vertices.find(1111));
assert(x == *g.vertex_by_property(Vertex{1111}));
assert(x == *g.vertex_by_property(g[x]));
}
add_edge(1111, 2222, g);
add_edge(2222, 1111, g);
print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
print_graph(g, get(&Vertex::other, g), std::cout << "---\n");
std::cout << "---\n";
for (auto *v : boost::make_iterator_range(vertices(g))) {
auto const& [id, other] = g[v];
std::cout << id << " " << std::quoted(other) << "\n";
}
if (auto v = g.vertex_by_property({1111})) {
std::cout << "==== removing " << g[*v].id << "\n";
clear_vertex(*v, g); // clear edges
remove_vertex(*v, g); // remove the vertex
}
print_graph(g, get(&Vertex::id, g), std::cout << "---\n");
print_graph(g, get(&Vertex::other, g), std::cout << "---\n");
}
Prints
---
1111 --> 2222
2222 --> 1111
---
default-constructed --> twotwotwotwo
twotwotwotwo --> default-constructed
---
1111 "default-constructed"
2222 "twotwotwotwo"
==== removing 1111
---
2222 -->
---
twotwotwotwo -->
Sidenote:
you cannot use an associative container selector for vertices. Specifying hash_mapS leads to unordered_set<void*, hash<void*>, equal_to<void*>, allocator<void*>> as the m_vertices type.
I try to implement a graph class based on https://stackoverflow.com/a/950173/7558038. When adding an edge I return the edge descriptor of the added edge, but if the edge already exists, it shouldn't be added. What shall I return then? Unfortunately, null_edge() does not exist (unlike null_vertex()). It could be an std::pair<e_it_t,bool> with an appropriate edge iterator type e_it_t, but how can I get an iterator to the new edge?
Don't use that class that is almost 10 years old. It is obsolete.
Bundled properties have come to BGL as long as I know, which is... probably since at least 2010. Nothing there is fundamentally easier than straight boost.
Another weird property is that somehow only complementary edges can be inserted in that graph. This might be what you want, but it doesn't warrant having the complete class, IMO.
In fact, having the custom type removes ADL, which makes things more tedious unless you go and add each other operation (like, you know, out_edges or in_edges, which presumably is what you wanted a bidirectional graph for in the first place; maybe you actually wish to have iterable ranges instead of pair<iterator, iterator> which requires you to write old-fashioned for loops).
Now that I've warmed up a bit, lets demonstrate:
Using The Obsolete Wrapper class
The linked wrapper affords usage like this:
struct VertexProperties { int i; };
struct EdgeProperties { double weight; };
int main() {
using MyGraph = Graph<VertexProperties, EdgeProperties>;
MyGraph g;
VertexProperties vp;
vp.i = 42;
MyGraph::Vertex v1 = g.AddVertex(vp);
g.properties(v1).i = 23;
MyGraph::Vertex v2 = g.AddVertex(vp);
g.properties(v2).i = 67;
g.AddEdge(v1, v2, EdgeProperties{1.0}, EdgeProperties{0.0});
for (auto vr = g.getVertices(); vr.first!=vr.second; ++vr.first) {
auto& vp = g.properties(*vr.first);
std::cout << "Vertex " << vp.i << "\n";
for (auto er = g.getAdjacentVertices(*vr.first); er.first!=er.second; ++er.first) {
auto s = *vr.first;
auto t = *er.first;
// erm how to get edge properties now?
std::cout << "Edge " << g.properties(s).i << " -> " << g.properties(t).i << " (weight?!?)\n";
}
}
}
Which prints:
Vertex 23
Edge 23 -> 67 (weight?!?)
Vertex 67
Edge 67 -> 23 (weight?!?)
Note I didn't exactly bother to solve the problem of getting the edge-weight (we don't readily get edge descriptors from the interface at all).
The for loops throw us back in time at least 6 years. And that's not nearly the worst problem. Presumably, you need your graph for something. Let's assume you want minimum cut, or a shortest path. This means you want to invoke an algorithm that requires the edge weight. This would look like so:
// let's find a shortest path:
// build the vertex index map
boost::property_map<MyGraph::GraphContainer, vertex_properties_t>::const_type vpmap =
boost::get(vertex_properties, g.getGraph());
// oops we need the id from it. No problem, it takes only rocket science:
struct GetId {
int operator()(VertexProperties const& vp) const {
return vp.i;
}
};
GetId get_id;
boost::transform_value_property_map<GetId,
boost::property_map<MyGraph::GraphContainer, vertex_properties_t>::const_type,
int> id_map
= boost::make_transform_value_property_map<int>(get_id, vpmap);
// build the weight map
boost::property_map<MyGraph::GraphContainer, edge_properties_t>::const_type epmap =
boost::get(edge_properties, g.getGraph());
// oops we need the weight from it. No problem, it takes only rocket science:
struct GetWeight {
double operator()(EdgeProperties const& ep) const {
return ep.weight;
}
};
GetWeight get_weight;
boost::transform_value_property_map<GetWeight,
boost::property_map<MyGraph::GraphContainer, edge_properties_t>::const_type,
double> weight_map
= boost::make_transform_value_property_map<double>(get_weight, epmap);
// and now we "simply" use Dijkstra:
MyGraph::vertex_range_t vertices = g.getVertices();
//size_t n_vertices = g.getVertexCount();
MyGraph::Vertex source = *vertices.first;
std::map<MyGraph::Vertex, MyGraph::Vertex> predecessors;
std::map<MyGraph::Vertex, double> distance;
boost::dijkstra_shortest_paths(g.getGraph(), source,
boost::predecessor_map(boost::make_assoc_property_map(predecessors))
.distance_map(boost::make_assoc_property_map(distance))
.weight_map(weight_map)
.vertex_index_map(id_map));
This is not my idea of usability. Just to show it all compiles and runs:
Live On Coliru
Replace The Wrapper In 2 Lines Of C++11
Let's replace the whole Graph class template in modern BGL style:
template <typename VertexProperties, typename EdgeProperties>
using Graph = adjacency_list<setS, listS, bidirectionalS, VertexProperties, EdgeProperties>;
Really. That is a solid replacement, I'll demonstrate it right away.
In fact, let's not do using namespace boost; because it pollutes our namespace with all manner of names we might find really useful (like, you know source or num_vertices) and invites ambiguous symbols:
template <typename VertexProperties, typename EdgeProperties>
using Graph = boost::adjacency_list<boost::setS, boost::listS, boost::bidirectionalS, VertexProperties, EdgeProperties>;
The Same Use-Cases - creation and dijkstra
They are still as simple, or in fact simpler. The full code goes down from 249 lines of code to just 57:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
namespace MyLib {
template <typename VertexProperties, typename EdgeProperties>
using Graph = boost::adjacency_list<boost::setS, boost::listS, boost::bidirectionalS, VertexProperties, EdgeProperties>;
}
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <iostream>
struct VertexProperties { int i; };
struct EdgeProperties { double weight; };
int main() {
using boost::make_iterator_range;
using MyGraph = MyLib::Graph<VertexProperties, EdgeProperties>;
MyGraph g;
auto v1 = add_vertex({42}, g);
auto v2 = add_vertex({42}, g);
g[v1].i = 23;
g[v2].i = 67;
add_edge(v1, v2, EdgeProperties{ 1.0 }, g);
add_edge(v2, v1, EdgeProperties{ 0.0 }, g);
for (auto v : make_iterator_range(vertices(g))) {
std::cout << "Vertex " << g[v].i << "\n";
}
for (auto e : make_iterator_range(boost::edges(g))) {
auto s = source(e, g);
auto t = target(e, g);
std::cout << "Edge " << g[s].i << " -> " << g[t].i << " (weight = " << g[e].weight << ")\n";
}
// let's find a shortest path:
auto id_map = get(&VertexProperties::i, g);
auto weight_map = get(&EdgeProperties::weight, g);
auto source = *vertices(g).first;
using Vertex = MyGraph::vertex_descriptor;
std::map<Vertex, Vertex> predecessors;
std::map<Vertex, double> distance;
std::map<Vertex, boost::default_color_type> colors;
boost::dijkstra_shortest_paths(
g, source,
boost::vertex_color_map(boost::make_assoc_property_map(colors))
.predecessor_map(boost::make_assoc_property_map(predecessors))
.distance_map(boost::make_assoc_property_map(distance))
.weight_map(weight_map)
.vertex_index_map(id_map));
}
I'd say
that is superior.
it's just as elegant despite not relying on using namespace boost (ADL is the key here)
and we actually printed the edge weight!
And It Can Be Cleaner Still
If you switch to a vertex container selector that has implicit vertex index (like vecS):
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
namespace MyLib {
template <typename VertexProperties, typename EdgeProperties>
using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::bidirectionalS, VertexProperties, EdgeProperties>;
}
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <iostream>
struct VertexProperties { int i; };
struct EdgeProperties { double weight; };
int main() {
using boost::make_iterator_range;
using MyGraph = MyLib::Graph<VertexProperties, EdgeProperties>;
MyGraph g;
add_vertex({23}, g);
add_vertex({67}, g);
add_edge(0, 1, EdgeProperties{ 1.0 }, g);
add_edge(1, 0, EdgeProperties{ 0.0 }, g);
for (auto v : make_iterator_range(vertices(g))) {
std::cout << "Vertex " << g[v].i << "\n";
}
for (auto e : make_iterator_range(boost::edges(g))) {
auto s = source(e, g);
auto t = target(e, g);
std::cout << "Edge " << g[s].i << " -> " << g[t].i << " (weight = " << g[e].weight << ")\n";
}
// let's find a shortest path:
std::vector<size_t> predecessors(num_vertices(g));
std::vector<double> distance(num_vertices(g));
boost::dijkstra_shortest_paths(g, *vertices(g).first,
boost::predecessor_map(predecessors.data()).distance_map(distance.data())
.weight_map(get(&EdgeProperties::weight, g)));
}
Output:
Vertex 23
Vertex 67
Edge 23 -> 67 (weight = 1)
Edge 67 -> 23 (weight = 0)
WAIT - Don't Forget About The Question!
I won't! I think the above shows the problem was an X/Y problem.
If you hadn't had the handicap of custom class wrapping, detecting duplicate edges was a given (see if add_vertex in BGL checks for the existence of the vertex for background):
struct { size_t from, to; double weight; } edge_data[] = {
{0, 1, 1.0},
{1, 0, 0.0},
{0, 1, 99.999} // oops, a duplicate
};
for(auto request : edge_data) {
auto addition = add_edge(request.from, request.to, { request.weight }, g);
if (!addition.second) {
auto& weight = g[addition.first].weight;
std::cout << "Edge already existed, changing weight from " << weight << " to " << request.weight << "\n";
weight = request.weight;
}
}
This will print Live On Coliru:
Edge already existed, changing weight from 1 to 99.999
If you prefer you can of course write things more expressively:
Graph::edge_descriptor e;
bool inserted;
boost::tie(e, inserted) = add_edge(request.from, request.to, { request.weight }, g);
Or, with some c++17 flair:
auto [e, inserted] = add_edge(request.from, request.to, { request.weight }, g);
More From Here
Also, in all likelihood you need to do uniqueness checks on the vertices too, so you end up with graph creation code like you can see in this answer: Boost BGL BFS Find all unique paths from Source to Target
Graph read_graph() {
std::istringstream iss(R"(
0 1 0.001
0 2 0.1
0 3 0.001
1 5 0.001
2 3 0.001
3 4 0.1
1 482 0.1
482 635 0.001
4 705 0.1
705 5 0.1
1 1491 0.01
1 1727 0.01
1 1765 0.01)");
Graph g;
std::map<int,Vertex> idx; // temporary lookup of existing vertices
auto vertex = [&](int id) mutable {
auto it = idx.find(id);
if (it != idx.end())
return it->second;
return idx.emplace(id, add_vertex(id, g)).first->second;
};
for (std::string line; getline(iss, line);) {
std::istringstream ls(line);
int s,t; double w;
if (ls >> s >> t >> w) {
add_edge(vertex(s), vertex(t), w, g);
} else {
std::cerr << "Skipped invalid line '" << line << "'\n";
}
}
return g;
}
Other examples show how you can insert both a -> b and b -> a while maintaining a mapping between the forward and back edges: Accessing specific edges in boost::graph with integer index
Summary
Coming full circle, I recommend getting acquainted with the newer, more elegant Boost Graph features. In the end, it's perfectly normal to encapsulate your graph, and you might end up with an even more polished interface.
I am new to C++ and the boost graph library. I am trying to use iterators to access information already stored within my graph "lattuce", more specifically, the weight an edge between two specific nodes.
This data will then be used by a A* algorithm (not the one in Boost graph). I am not sure if iterators are the solution to this either, so any guidance or criticism would be appreciated.
struct Point {//struct point with vertex properties
int x, y;
int parentx, parenty;
double g;
double h;
friend std::ostream& operator<<(std::ostream& os, Point p) {
return os << "[" << p.x << "," << p.y << "]";
}
};
int main() {
//declarations
typedef property < edge_weight_t, double >Weight;
using std::vector;//?
using Graph = adjacency_list<setS, vecS, undirectedS, Point, Weight>;//graph includes our created point struct property<edge_weight_t
using vertex_descriptor = Graph::vertex_descriptor;
Graph lattuce;
//lattuce graph is created with weighted edges value 1 or 1,41 if diagonal. The functions used on a loop are:
//add_edge(nodes[p.x][p.y],nodes[neighbour.x][neighbour.y], Weight(1.0), lattuce);
//add_edge(nodes[p.x][p.y],nodes[neighbour.x][neighbour.y], Weight(1.4), lattuce);
If more information about the code that generates the graph is needed I'll provide it. Thanks
It is possible to obtain link edge weights in directed and undirected graphs by means of the boost::property_map:
boost::property_map<UndirectedGraph, boost::edge_weight_t>::type EdgeWeightMap = get(boost::edge_weight_t(), g);
Example implementation given below, that first builds the following simple graph (specifically a tree with no cycles):
... then uses the boost::property_map to obtain the weight of each edge, and prints it out:
#include <iostream>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
typedef boost::property<boost::edge_weight_t, double> EdgeWeight;
typedef boost::adjacency_list<boost::listS, boost::vecS, boost::undirectedS, boost::no_property, EdgeWeight> UndirectedGraph;
typedef boost::graph_traits<UndirectedGraph>::edge_iterator edge_iterator;
int main(int, char*[])
{
// 1. Undirected graph - print out the edge weights
UndirectedGraph g;
boost::add_edge(0, 1, 8, g);
boost::add_edge(0, 5, 2, g);
boost::add_edge(5, 6, 1, g);
boost::add_edge(4, 5, 5, g);
boost::add_edge(3, 5, 7, g);
boost::property_map<UndirectedGraph, boost::edge_weight_t>::type EdgeWeightMap = get(boost::edge_weight_t(), g);
std::pair<edge_iterator, edge_iterator> edgePair;
for (edgePair = edges(g); edgePair.first != edgePair.second; ++edgePair.first)
{
std::cout << *edgePair.first << " " << EdgeWeightMap[*edgePair.first] << std::endl;
}
return 0;
}
Giving the following console output, showing the edges as (start,end) node pairs plus their respective weights:
I have written an algorithm which does (some sort of) 'topological sorting' (not exact). This algorithm copies the given graph and then manipulates the copy (by removing edges). On a million node boost graph, if my algorithm takes 3.1 seconds, 2.19 seconds are consumed by copying the given graph into a new one.
Can I remove edges without actually removing them permanently e.g. sort of masking in boost::graph library? And when algorithm is done, I unmask all edges the graph regains it original state. I suspect this should make my algorithm run much faster.
Boost.Graph's filtered_graph seems a good fit for what you want. Unfortunately I really have no idea if it will perform better than your current approach (I suspect it will). If you decide to implement this approach I would love to hear about the results.
Example on LWS.
#include <iostream>
#include <tuple>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/topological_sort.hpp>
#include <boost/unordered_set.hpp>
struct Vertex
{
Vertex(){}
Vertex(int val):name(val){}
int name;
};
typedef boost::adjacency_list<boost::vecS,boost::vecS,boost::directedS,Vertex> graph_type;
typedef boost::graph_traits<graph_type>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<graph_type>::edge_descriptor edge_descriptor;
// A hash function for edges.
struct edge_hash:std::unary_function<edge_descriptor, std::size_t>
{
edge_hash(graph_type const& g):g(g){}
std::size_t operator()(edge_descriptor const& e) const {
std::size_t seed = 0;
boost::hash_combine(seed, source(e,g));
boost::hash_combine(seed, target(e,g));
//if you don't use vecS as your VertexList container
//you will need to create and initialize a vertex_index property and then use:
//boost::hash_combine(seed,get(boost::vertex_index, g, source(e,g)));
//boost::hash_combine(seed,get(boost::vertex_index, g, target(e,g)));
return seed;
}
graph_type const& g;
};
typedef boost::unordered_set<edge_descriptor, edge_hash> edge_set;
typedef boost::filtered_graph<graph_type,boost::is_not_in_subset<edge_set> > filtered_graph_type;
template <typename Graph>
void print_topological_order(Graph const& g)
{
std::vector<vertex_descriptor> output;
topological_sort(g,std::back_inserter(output));
std::vector<vertex_descriptor>::reverse_iterator iter=output.rbegin(),end=output.rend();
for(;iter!=end;++iter)
std::cout << g[*iter].name << " ";
std::cout << std::endl;
}
int main()
{
graph_type g;
//BUILD THE GRAPH
vertex_descriptor v0 = add_vertex(0,g);
vertex_descriptor v1 = add_vertex(1,g);
vertex_descriptor v2 = add_vertex(2,g);
vertex_descriptor v3 = add_vertex(3,g);
vertex_descriptor v4 = add_vertex(4,g);
vertex_descriptor v5 = add_vertex(5,g);
edge_descriptor e4,e5;
add_edge(v0,v1,g);
add_edge(v0,v3,g);
add_edge(v2,v4,g);
add_edge(v1,v4,g);
std::tie(e4,std::ignore) = add_edge(v4,v3,g);
std::tie(e5,std::ignore) = add_edge(v2,v5,g);
//GRAPH BUILT
std::cout << "Original graph:" << std::endl;
print_topological_order(g);
edge_hash hasher(g);
edge_set removed(0,hasher); //need to pass "hasher" in the constructor since it is not default constructible
filtered_graph_type fg(g,removed); //creates the filtered graph
removed.insert(e4); //you can "remove" edges from the graph by adding them to this set
removed.insert(e5);
std::cout << "Filtered Graph after \"removing\" 2 edges" << std::endl;
print_topological_order(fg);
removed.clear(); //clearing the set restores your original graph
std::cout << "Filtered Graph after resetting" << std::endl;
print_topological_order(fg);
}