7

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.

Community
  • 1
  • 1
Emmanuel Jay
  • 484
  • 3
  • 10
  • 1
    @Sehe it's not a duplicate at all.. – Erowlin Mar 30 '15 at 16:19
  • @Erowlin I suppose it would have been a tiny bit nice if you had used actual arguments with that. My arguments are in the linked answer. I take it you know a better answer to this question then? I happily invite you to post it. – sehe Mar 30 '15 at 18:27
  • @Erowlin Anyhow, to make it more obvious how the question was related, I've proceded to explain that in an answer that ought to be comprehensive. May I ask what made you so confident to say it wasn't a duplicate? (If I had to guess it was the absense of the word "dijkstra"?). – sehe Mar 30 '15 at 21:13
  • Sorry about the lack of explanations. What missed in the other question was a clear explanation like you posted. Thank you. I think that too much questions are marked as "duplicate" without a lot of explanations. – Erowlin Mar 31 '15 at 09:46
  • @Erowlin In this case I really think it was warranted, because the question was the same (we have multiple weights for each edge) and the answer too :) In my opinion people are frequently far too dismissive of good tips if they're not spelling things out. (That hurts both the asker and the answerer). I don't mind now. I get a nice answer that I can reuse in the future :) – sehe Mar 31 '15 at 11:52
  • @EmmanualJay Cheers for the update – sehe Jun 10 '15 at 17:33

1 Answers1

17

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.

enter image description here


¹ 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

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I don't know how I can thank you enough ! Indeed the response in the other question was very close to what I wanted, but to be honest, I was missing the few step that you very clearly explained in your response. Thank you very much ! I did implement what I wanted, your code was very clear. – Emmanuel Jay Mar 31 '15 at 12:08
  • 3
    @sehe ever thought of writing a BGL book? I'd buy it. – pbible Apr 03 '15 at 12:06
  • 2
    @pbible Mmm. I'll do the [Boost Spirit book](http://stackoverflow.com/questions/29368279/compile-error-when-adding-semantic-action-to-boost-spirit-parser/29368752#comment46919610_29368752) first :) (It doesn't help I don't really grok graph theory. I do grok c++ a bit though) – sehe Apr 03 '15 at 12:07
  • One of the few that grok c++. I've been programming for year and barely understand it lol. – pbible Apr 03 '15 at 12:10
  • TBH I think surprisingly many people do grok C++. Or Haskell, for that matter. – sehe Apr 03 '15 at 12:17
  • @sehe Sorry to bother you again, but we are trying to compile your exact code of the make_function_property_map on Visual Studio 2012 (version 10.0), and we have a nasty error that we couldn't fix nor understand over the past days... There is a conflic of some sort : "1>c:\boost_1_58_0\boost\utility\result_of.hpp(189): error C2825: 'F' : needs to be a class or a namespace when followed by '::'". If you have any clue, we would be in your debt. – Emmanuel Jay Jun 08 '15 at 16:56
  • Emmanuel have you previously compiled it on any MSVC++ compiler? If so what version was it (also what version of boost) – sehe Jun 08 '15 at 17:11
  • @sehe your solution does not work for VS2013. I'm trying with boost 1.58.0. It fails to compile, and there's hardly any documentation on function property maps. – dev_nut Apr 25 '17 at 04:09
  • This is extremely helpful! I am trying to use it with a vector of weighs and it seems to work. However, in the call `auto make_iterator_range(vertices(g));`, I get the error message `error: no match for call to ‘(std::vector) (graph_t&)’`. Would you perhaps know what's wrong? Many thanks. – Toon Aug 21 '18 at 12:56
  • @Toon You're calling `make_iterator_property_map` but `vertices(g)` isn't an iterator. I can only guess you named the wrong function (perhaps you wanted to type `make_iterator_range`). – sehe Aug 21 '18 at 12:59
  • Yes that was wrong, and looks like you read it before I realized it and edited the comment. With `make_iterator_range` it gives me the same error, though: `no match for call to ‘(std::vector) (graph_t&)’`. I checked my adjacency list to see whether there are differences with your example, but to no avail... – Toon Aug 21 '18 at 13:05
  • For some reason `vertices(g)` did not work. I resorted to another method to fixing it, which is so ugly that I will not share it here! – Toon Aug 21 '18 at 13:20
  • @Toon I have the suspicion you could have asked that particular snag in a separate question. I can't see your code so I can't really guess what is happening. Try `boost::vertices(g)` though. – sehe Aug 21 '18 at 13:55
  • @Sehe, it was a little bit more involved, but that was the answer, basically. I didn't want to create a new question because it looked so trivial. Maybe I should have. I guess it's best to leave it at that :-) Thanks! – Toon Aug 29 '18 at 08:54