Related
I'm implementing an algorithm in C++ with Boost Graph.
I want to find all the vertex in the neighborhood of v (so, all its neighbors), then change a property of their and finally clear all of their edges.
I found in Boost the function adjacent_vertices(v,g) (where v is the vertex and g is the graph) to find all the neighbors. Then I want to apply on all of them the function clear_vertex(v,g) (again, v is the vertex and g is the graph) to remove all of their edges.
At this point, I have a problem. The adjacent_vertices function returns a pair of adjacency_iterator, while for the clear_vertex function I need vertex_iterator (if I understand correctly how these functions work).
So, there is an easy way to transform the adjacency_iterator in vertex_iterator? If I keep the adjacency_iterator and pass it to the clear_vertex function, the problem is that it doesn't remove the edges (or remove them randomly to some vertices).
My wrong code is:
Graph::adjacency_iterator v,vend;
for(boost::tie(v,vend) = neighbours; v != vend ; ++v) {
clear_vertex(*v,g2);
}
It depends on the edge container selectors.
The easiest way is when the containers are node-based, i.e. only the iterators/descriptors to any removed edges are invalidated.
Another way is when you split the "query" and "modification" aspects, e.g.
Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/random.hpp>
#include <random>
void clear_all_neighbours(auto v, auto& g) {
auto neigh = adjacent_vertices(v, g);
std::set to_clear(neigh.first, neigh.second);
for (auto u : to_clear)
clear_vertex(u, g);
}
int main()
{
std::mt19937 prng(std::random_device{}());
boost::adjacency_list<> g;
generate_random_graph(g, 1000,2000, prng);
std::cout << "Before: " << num_edges(g) << "\n";
auto v = vertex(prng() % num_vertices(g), g);
clear_all_neighbours(v, g);
std::cout << "After: " << num_edges(g) << "\n";
}
Possible output:
Before: 2000
After: 1983
The BGL aims to be as generic as possible. Therefore I have written for an interface for another piece of software that also aims to be as generic as possible. However, I get problems, when the vertex_descriptor is not valid.
Here is a minimal example that describes the problem:
template <class Graph>
class Wrapper {
public:
T& get_property_obj(const typename Graph::vertex_descriptor& v) {
// This should be inserted:
// if (invalid_descriptor(v)) {
// throw something;
// }
return g[v];
}
private:
Graph& g;
};
The problem is that when Graph is a boost::adjacency_list with vecT as vertex storage type the Graph::operator[ does not seem to check if v is valid and can deliver invalid memory.
Can I somehow do a simple check in the wrapper class that v is valid?
Obviously the simplest solution is to iterate over all vertices and check for equality but for the given example a check of vertex_descriptor and num_vertices() would be sufficient (but of course not generic).
There is no way to tell whether a vertex descriptor is valid.
You can only check
the range (in case of integral vertex descriptors, like with vecS)
by looking it up for node-based vertex storage descriptors.
However, both scenarios risk giving you the wrong results, for the same reason that standard library containers do:
std::vector<int> is{1,2,3};
auto i1 = is.begin();
is.push_back(4);
std::cout << "Undefined Behaviour: " << *i1;
The point is, the iterator (or descriptor as the case may be) has been invalidated. There's no way to detect this happened¹, you'll always have to deal with it yourself.
The iterator/descriptor invalidation guarantees follow the guarantees of the underlying containers though. This means that for node-based containers, you can actually rely on descriptors (and references) being stable across insertion and even deletion (except for the element that was deleted, obviously).
See e.g. Iterator invalidation rules
So for integral descriptors, you'd write:
bool descriptor_looks_valid(vertex_descriptor v) const {
return v>=0 && v < num_vertices(g);
}
As you know, it'll be horribly inefficient for most other container selectors:
bool descriptor_looks_valid(vertex_descriptor v) const {
auto vds = vertices(g);
return std::count(vds.first, vds.second, v);
}
Or generically (assuming c++17):
bool descriptor_looks_valid(vertex_descriptor v) const {
if constexpr(std::is_integral_v<vertex_descriptor>) {
return v>=0 && v < num_vertices(g);
} else {
auto vds = vertices(g);
return std::count(vds.first, vds.second, v);
}
}
Demonstration Of The Perils
This tiny demo shows the perils of mistaking "range check pass" for "valid". This program repeats this:
template <typename vertexS> void doTest() {
using Graph = boost::adjacency_list<
boost::vecS,
vertexS,
boost::directedS,
PropertyObj>;
Graph g;
auto v1 = add_vertex({"one"}, g);
auto v2 = add_vertex({"two"}, g);
auto v3 = add_vertex({"three"}, g);
auto v4 = add_vertex({"four"}, g);
auto v5 = add_vertex({"five"}, g);
Wrapper w = g;
std::cout << w.get_property_obj(v3).something << std::endl;
// but this is confounding, and only accidentally "works" for vecS:
remove_vertex(v1, g);
std::cout << w.get_property_obj(v3).something << std::endl;
try {
// this looks "valid" with vecS, but should throw for listS
//
// of course, like with v3 the descriptor was already invalidated both cases
std::cout << w.get_property_obj(v1).something << std::endl;
} catch(std::range_error const& re) {
std::cout << "(range_error cautgh: " << re.what() << "\n";
}
}
For vertexS equal to vecS, listS or setS. Typical output is Live On Coliru:
Testing with vecS:
three
four
two
Testing with listS:
three
three
(range_error caught: get_property_obj
Testing with setS:
three
three
(range_error caught: get_property_obj
Conclusion
The reason validity checks aren't implemented is because the underlying containers do not support them.
Also, although you can "approximate" a validation this will only prevent against crashes, not against unspecified behavior.
In fact, depending on what semantics are expected you might trigger Undefined Behavior just the same (e.g. if you assume that get_property_obj(v3) yields the same value each time, you will have broken code with vecS).
Can I somehow do a simple check in the wrapper class that v is valid?
In short, no.Descriptor validity is a function of the usage patterns and the caller will have to account for it.
Full Listing
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <iostream>
struct PropertyObj {
std::string something;
};
template <class Graph, class T = PropertyObj>
class Wrapper {
public:
using vertex_descriptor = typename Graph::vertex_descriptor;
T& get_property_obj(vertex_descriptor v) {
if (!descriptor_looks_valid(v))
throw std::range_error("get_property_obj");
return g[v];
}
Wrapper(Graph& g) : g(g){}
private:
bool descriptor_looks_valid(vertex_descriptor v) const {
if constexpr(std::is_integral_v<vertex_descriptor>) {
return v>=0 && v < num_vertices(g);
} else {
auto vds = vertices(g);
return std::count(vds.first, vds.second, v);
}
}
Graph& g;
};
template <typename vertexS> void doTest() {
using Graph = boost::adjacency_list<
boost::vecS,
vertexS,
boost::directedS,
PropertyObj>;
Graph g;
auto v1 = add_vertex({"one"}, g);
auto v2 = add_vertex({"two"}, g);
auto v3 = add_vertex({"three"}, g);
auto v4 = add_vertex({"four"}, g);
auto v5 = add_vertex({"five"}, g);
boost::ignore_unused_variable_warning(v1);
boost::ignore_unused_variable_warning(v2);
boost::ignore_unused_variable_warning(v3);
boost::ignore_unused_variable_warning(v4);
boost::ignore_unused_variable_warning(v5);
Wrapper w = g;
std::cout << w.get_property_obj(v3).something << std::endl;
// but this is confounding, and only accidentally "works" for vecS:
remove_vertex(v1, g);
std::cout << w.get_property_obj(v3).something << std::endl;
try {
// this looks "valid" with vecS, but should throw for listS
//
// of course, like with v3 the descriptor was already invalidated both cases
std::cout << w.get_property_obj(v1).something << std::endl;
} catch(std::range_error const& re) {
std::cout << "(range_error caught: " << re.what() << "\n";
}
}
int main() {
std::cout << "Testing with vecS:\n";
doTest<boost::vecS>();
std::cout << "\nTesting with listS:\n";
doTest<boost::listS>();
std::cout << "\nTesting with setS:\n";
doTest<boost::setS>();
}
¹ although some library implementations have extensions that allow you to detect it some of the time - like https://learn.microsoft.com/en-us/cpp/standard-library/debug-iterator-support?view=vs-2019
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 learning the Fruchterman-Reingold algorithm in Boost Graph Library. By reading the document, I know that the algorithm is to compute the positions for all nodes in terms of graph layout, but my problem is I cannot understand the calculation steps of attractive forces in Boost Graph Library.
For example, if the topology is rectangle with height 100 and width 100, each vertex is labelled as string, and the relation between each pair vertex as:
"0" "5"
"Kevin" "Martin"
"Ryan" "Leo"
"Y" "S"
"Kevin" "S"
"American" "USA"
Each row denotes the two labelled vertices are connected. The formula of attractive force for each vertex is supposed to be:
f = (d^2) / k
where d is the distance between two vertices and k is the optimal distances. But I don't understand how to get the distance d in the code of Fruchterman-Reingold in Boost Graph Library. In this example, does it compute the ASCII value difference between each pair vertices as the distance d? (ASCII value of '0' is 48, and ASCII value of '5' is 53. Is it true that Fruchterman-Reingold computes 53 - 48 = 5 as d in BGL?) I really appreciate if anyone can help me.
Furchterman-Reingold implementation takes an IN/OUT topology.
It expects the topology to be initialized to some state before execution. The distance passed to the attraction function will be the one from the topology at that iteration.
Note Note that (unless progressive is set to true) Furterman-Reingold will initialize the topology randomly by default (using random_graph_layout).
All the above taken from in the documentation.
Here's a tiny demo using your input graph that shows how to implement such an attractive_force function:
struct AttractionF {
template <typename EdgeDescriptor, typename Graph>
double operator()(EdgeDescriptor /*ed*/, double k, double d, Graph const& /*g*/) const {
//std::cout << "DEBUG af('" << g[source(ed, g)].name << " -> " << g[target(ed, g)].name << "; k:" << k << "; d:" << d << ")\n";
return (d*d/k);
}
};
See Live On Coliru
#include <memory>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/fruchterman_reingold.hpp>
#include <boost/graph/random_layout.hpp>
#include <libs/graph/src/read_graphviz_new.cpp>
#include <boost/graph/topology.hpp>
#include <boost/random.hpp>
using namespace boost;
struct Vertex {
std::string name;
};
struct AttractionF {
template <typename EdgeDescriptor, typename Graph>
double operator()(EdgeDescriptor /*ed*/, double k, double d, Graph const& /*g*/) const {
//std::cout << "DEBUG af('" << g[source(ed, g)].name << " -> " << g[target(ed, g)].name << "; k:" << k << "; d:" << d << ")\n";
return (d*d/k);
}
};
using Graph = adjacency_list<vecS, vecS, undirectedS, Vertex>;
Graph make_sample();
int main() {
auto g = make_sample();
using Topology = square_topology<boost::mt19937>;
using Position = Topology::point_type;
std::vector<Position> positions(num_vertices(g));
square_topology<boost::mt19937> topology;
random_graph_layout(g,
make_iterator_property_map(positions.begin(), boost::identity_property_map{}),
topology);
fruchterman_reingold_force_directed_layout(
g,
make_iterator_property_map(positions.begin(), boost::identity_property_map{}),
topology,
attractive_force(AttractionF())
);
dynamic_properties dp;
dp.property("node_id", get(&Vertex::name, g));
write_graphviz_dp(std::cout, g, dp);
}
Graph make_sample() {
std::string const sample_dot = R"(
graph {
"0" -- "5";
"Kevin" -- "Martin";
"Ryan" -- "Leo";
"Y" -- "S";
"Kevin" -- "S";
"American" -- "USA";
}
)";
Graph g;
dynamic_properties dp;
dp.property("node_id", get(&Vertex::name, g));
read_graphviz(sample_dot, g, dp);
return g;
}
Note that in c++11 you can equally well use a lambda:
fruchterman_reingold_force_directed_layout(
g,
make_iterator_property_map(positions.begin(), boost::identity_property_map{}),
topology,
attractive_force([](Graph::edge_descriptor, double k, double d, Graph const&) { return (d*d)/k; })
);
I have a boost graph with multiples weights for each edges (imagine one set of weights per hour of the day). Every one of those weights values is stored in a propretyEdge class :
class propretyEdge {
std::map<std::string,double> weights; // Date indexed
}
I created a graph with those properties, and then filled it with the right values.
The problem is now that I want to launch the Dijkstra algorithm over a particular set of weight on the graph : for example a function that could be :
void Dijkstra (string date, parameters ... )
That would use the
weights[date]
value for each Edge of the graph.
I read over and over the documentation, and I couldn't have a clear picture of what I have to do. I surely need to write something like this, but I have no idea were to start :
boost::dijkstra_shortest_paths (
(*graph_m),
vertex_origin_num_l,
// weight_map (get (edge_weight, (*graph_m)))
// predecessor_map(boost::make_iterator_property_map(predecessors.begin(), get(boost::vertex_index, (*graph_m)))).
// distance_map(boost::make_iterator_property_map(distances.begin (), get(vertex_index,(*graph_m) )))
predecessor_map(predecessorMap).
distance_map(distanceMap)
);
Thank you for your help.
Edit
Thanks to the wonderful Answer of Sehe, I was able to do exactly what I wanted on MacOS and on Ubuntu.
But when we tried to compile this piece of code on Visual Studio 2012, it appeared that VS wasn't very good at understanding pointer function of boost. So we modified the part of Sehe :
auto dated_weight_f = [&](Graph::edge_descriptor ed) {
return g[ed].weights.at(date);
};
auto dated_weight_map = make_function_property_map<Graph::edge_descriptor, double>(dated_weight_f);
by :
class dated_weight_f {
public:
dated_weight_f(Graph* graph_p,std::string date_p){
graph_m=graph_p;
date_m=date_p;
}
typedef double result_type;
result_type operator()(Edge edge_p) const{
return (*graph_m)[edge_p].weights.at(date_m);
}
private:
Graph* graph_m;
std::string date_m;
};
const auto dated_weight_map = make_function_property_map<Edge>(dated_weight_f(graph_m,date_l));
Which had the advantage of not using a pointer function.
Since it's apparently not immediately clear that this question is answered in the other answer, I'll explain.
All you really need is a custom weight_map parameter that is "stateful" and can select a certain value for a given date.
You can make this as complicated as you wish ¹, so you could even interpolate/extrapolate a weight given an unknown date ², but let's for the purpose of this demonstration keep it simple.
Let's define the graph type (roughly) as above:
struct propretyEdge {
std::map<std::string, double> weights; // Date indexed
};
using Graph = adjacency_list<vecS, vecS, directedS, no_property, propretyEdge>;
Now, let's generate a random graph, with random weights for 3 different dates:
int main() {
Graph g;
std::mt19937 prng { std::random_device{}() };
generate_random_graph(g, 8, 12, prng);
uniform_real<double> weight_dist(10,42);
for (auto e : make_iterator_range(edges(g)))
for (auto&& date : { "2014-01-01", "2014-02-01", "2014-03-01" })
g[e].weights[date] = weight_dist(prng);
And, jumping to the goal:
for (std::string const& date : { "2014-01-01", "2014-02-01", "2014-03-01" }) {
Dijkstra(date, g, 0);
}
}
Now how do you implement Dijkstra(...)? Gleaning from the documentation sample, you'd do something like
void Dijkstra(std::string const& date, Graph const& g, int vertex_origin_num_l = 0) {
// magic postponed ...
std::vector<Graph::vertex_descriptor> p(num_vertices(g));
std::vector<double> d(num_vertices(g));
std::vector<default_color_type> color_map(num_vertices(g));
boost::typed_identity_property_map<Graph::vertex_descriptor> vid; // T* property maps were deprecated
dijkstra_shortest_paths(g, vertex_origin_num_l,
weight_map(dated_weight_map).
predecessor_map(make_iterator_property_map(p.data(), vid)).
distance_map(make_iterator_property_map(d.data(), vid)).
color_map(make_iterator_property_map(color_map.data(), vid))
);
Now the only unclear bit here should be dated_weight_map.
Enter Boost Property Maps
As I showed in the linked Is it possible to have several edge weight property maps for one graph BOOST?, you can have all kinds of property maps ³, including invocation of user-defined functions. This is the missing piece:
auto dated_weight_f = [&](Graph::edge_descriptor ed) {
return g[ed].weights.at(date);
};
auto dated_weight_map = make_function_property_map<Graph::edge_descriptor, double>(dated_weight_f);
Voilà: done
I hope that by now, the correspondence in the question as well as the answer of the linked question is clear. All that's left to do is post the full live sample and the outcome in a pretty picture:
Live On Coliru
#include <boost/property_map/property_map.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <boost/property_map/property_map_iterator.hpp>
#include <random>
#include <boost/graph/random.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <fstream>
using namespace boost;
struct propretyEdge {
std::map<std::string, double> weights; // Date indexed
};
using Graph = adjacency_list<vecS, vecS, directedS, no_property, propretyEdge>;
void Dijkstra(std::string const& date, Graph const& g, int vertex_origin_num_l = 0) {
auto dated_weight_f = [&](Graph::edge_descriptor ed) {
return g[ed].weights.at(date);
};
auto dated_weight_map = make_function_property_map<Graph::edge_descriptor, double>(dated_weight_f);
std::vector<Graph::vertex_descriptor> p(num_vertices(g));
std::vector<double> d(num_vertices(g));
std::vector<default_color_type> color_map(num_vertices(g));
boost::typed_identity_property_map<Graph::vertex_descriptor> vid; // T* property maps were deprecated
dijkstra_shortest_paths(g, vertex_origin_num_l,
weight_map(dated_weight_map).
predecessor_map(make_iterator_property_map(p.data(), vid)).
distance_map(make_iterator_property_map(d.data(), vid)).
color_map(make_iterator_property_map(color_map.data(), vid))
);
std::cout << "distances and parents for '" + date + "':" << std::endl;
for (auto vd : make_iterator_range(vertices(g)))
{
std::cout << "distance(" << vd << ") = " << d[vd] << ", ";
std::cout << "parent(" << vd << ") = " << p[vd] << std::endl;
}
std::cout << std::endl;
std::ofstream dot_file("dijkstra-eg-" + date + ".dot");
dot_file << "digraph D {\n"
" rankdir=LR\n"
" size=\"6,4\"\n"
" ratio=\"fill\"\n"
" graph[label=\"shortest path on " + date + "\"];\n"
" edge[style=\"bold\"]\n"
" node[shape=\"circle\"]\n";
for (auto ed : make_iterator_range(edges(g))) {
auto u = source(ed, g),
v = target(ed, g);
dot_file
<< u << " -> " << v << "[label=\"" << get(dated_weight_map, ed) << "\""
<< (p[v] == u?", color=\"black\"" : ", color=\"grey\"")
<< "]";
}
dot_file << "}";
}
int main() {
Graph g;
std::mt19937 prng { std::random_device{}() };
generate_random_graph(g, 8, 12, prng);
uniform_real<double> weight_dist(10,42);
for (auto e : make_iterator_range(edges(g)))
for (auto&& date : { "2014-01-01", "2014-02-01", "2014-03-01" })
g[e].weights[date] = weight_dist(prng);
for (std::string const& date : { "2014-01-01", "2014-02-01", "2014-03-01" }) {
Dijkstra(date, g, 0);
}
}
Output, e.g.
¹ As long as you keep the invariants required by the algorithm you're invoking. In particular, you must return the same weight consistently during the execution, given the same edge. Also, some algorithms don't support negative weight etc.
² I'd highly suggest using a Boost ICL interval_map in such a case but I digress
³ see also map set/get requests into C++ class/structure changes