Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Boost::graph Dijkstra and custom objects and properties

I want to use boost's dijkstra algorithm (since I'm using boost in other parts of my program). The problem I'm having is adding custom objects (I believe they are referred to as property) to the adjacency_list.

Essentially I have a custom edge class that maintains all sorts of information regarding the edge and the vertices that are connected through it. I want to store my custom data object with the edge properties that are required by the adjaceny_list

I've successfully implemented the toy example that boost provides. I've tried to use custom properties to no avail (boost example, boost properties). I'm fine with just encapsulating my VEdge data structure in a struct or something, I just need to be able to retrieve it. But I haven't been able to figure out how to include my custom data structure into the boost adjaceny_list structure.

In my case I have the following program:

Main.cpp:

#include <iostream>
#include <fstream>
#include "dijkstra.h"
#include <vector>

int
main(int, char *[])
{
  // Generate the vector of edges from elsewhere in the program
  std::vector<VEdge*> edges; //someclass.get_edges();

  td* test = new td(edges);
  test->run_d();

  test->print_path();

  return EXIT_SUCCESS;
}

Dijkstra.cpp:

#include <iostream>
#include <fstream>
#include "dijkstra.h"

using namespace boost;

td::td() {
    kNumArcs = sizeof(kEdgeArray) / sizeof(Edge);
    kNumNodes = 5;
}

td::td(std::vector<VEdge*> edges) {
    // add edges to the edge property here
    for(VEdge* e : edges) {
        // for each edge, add to the kEdgeArray variable in some way
        // The boost example forces the input to be an array of edge_property type.  
        // So here is where I will convert my raw VEdge data structure to 
        // the custom edge_property that I am struggling to understand how to create.
    }
    kNumArcs = sizeof(kEdgeArray) / sizeof(Edge);
    kNumNodes = 5;
}

void td::run_d() {
    kGraph = graph_t(kEdgeArray, kEdgeArray + kNumArcs, kWeights, kNumNodes);

    kWeightMap = get(edge_weight, kGraph);
    kP = std::vector<vertex_descriptor >(num_vertices(kGraph));
    kD = std::vector<int>(num_vertices(kGraph));
    kS = vertex(A, kGraph);

    dijkstra_shortest_paths(kGraph, kS,
            predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph))).
                    distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph))));
}

void td::print_path() {
    std::cout << "distances and parents:" << std::endl;
    graph_traits < graph_t >::vertex_iterator vi, vend;
    for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
        std::cout << "distance(" << kName[*vi] << ") = " << kD[*vi] << ", ";
        std::cout << "parent(" << kName[*vi] << ") = " << kName[kP[*vi]] << std::
        endl;
    }
}

void td::generate_dot_file() {
    std::cout << std::endl;

    std::ofstream dot_file("figs/dijkstra-eg.dot");

    dot_file << "digraph D {\n"
            << "  rankdir=LR\n"
            << "  size=\"4,3\"\n"
            << "  ratio=\"fill\"\n"
            << "  edge[style=\"bold\"]\n" << "  node[shape=\"circle\"]\n";

    graph_traits < graph_t >::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
        graph_traits < graph_t >::edge_descriptor e = *ei;
        graph_traits < graph_t >::vertex_descriptor
                u = source(e, kGraph), v = target(e, kGraph);
        dot_file << kName[u] << " -> " << kName[v]
                << "[label=\"" << get(kWeightMap, e) << "\"";
        if (kP[v] == u)
            dot_file << ", color=\"black\"";
        else
            dot_file << ", color=\"grey\"";
        dot_file << "]";
    }
    dot_file << "}";
}

Dijkstra.h:

#ifndef _TEMPD_H_
#define _TEMPD_H_

#pragma once

#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>

using namespace boost;

typedef adjacency_list < listS, vecS, directedS,
        no_property, property < edge_weight_t, int > > graph_t;
typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor;
typedef std::pair<int, int> Edge;

struct VEdge{
    // custom variables here
    VNode start;
    VNode end;
    int weight;
    int id;
    // other irrelevant data pertinent to my program that must be preserved
};

struct VNode {
    // custom variables here
    int x;
    int y;
    int id;
    // other irrelevant data pertinent to my program that must be preserved
}

enum nodes { A, B, C, D, E };

class td {
public:
    td();
    td(std::vector<VEdge*>);
    ~td();

    void run_d();

    void print_path();
    void generate_dot_file();
private:
    Edge kEdgeArray[9] = { Edge(A, C), Edge(B, B), Edge(B, D), Edge(B, E),
            Edge(C, B), Edge(C, D), Edge(D, E), Edge(E, A), Edge(E, B)
    };
    char kName[5] = {'A','B','C','D','E'};
    int kWeights[9] = { 1, 2, 1, 2, 7, 3, 1, 1, 1 };
    int kNumArcs;
    int kNumNodes;
    vertex_descriptor kS;
    graph_t kGraph;
    std::vector<int> kD;
    std::vector<vertex_descriptor> kP;
    property_map<graph_t, edge_weight_t>::type kWeightMap;
};
#endif

I know my example is a bit contrived, but it communicates what I'm trying to accomplish. I know I need a custom data structure for my edge_descriptor which gets sent to the graph_t typedef.

So I'm looking to alter my Dijkstra.h file to look something like this:

struct vertex_label_t {vertex_property_tag kind;};
struct edge_label_t {edge_property_tag kind;};

typedef property <vertex_custom_t, VNode*>,
    property <vertex_label_t, string>,
        property <vertex_root_t, ing> > > vertex_p;

typedef property <edge_custom_t, VEdge*>,
    property <edge_label_t, string > > edge_p;

typedef adjacency_list < listS, vecS, directedS,
        vertex_p, edge_p > graph_t;
typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor;
like image 610
lilott8 Avatar asked Mar 10 '15 20:03

lilott8


1 Answers

Okay. You've come a long ways since https://stackoverflow.com/questions/28889423/boost-adjacency-list-swap-errors-when-using-boost-dijkstra; the sample is self-contained and can compile¹

I figured I could just connect some dots and hope this would be helpful.

1. Using VEdge

For the simplest option, I'd use Bundled Properties, and define VEdge as follows:

struct VEdge {
    int id;
    int source, target;
    double weight;
    // custom variables here
};

Now, we define the graph as

using graph_t = boost::adjacency_list<boost::listS, boost::vecS, 
                    boost::directedS, boost::no_property, VEdge>;
using weight_map_t = boost::property_map<graph_t, double VEdge::*>::type;

As you can see the weight-map has a little more complicated type, as documented under Properties maps from bundled properties. You can get the actual map:

weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);

Now, let's recreate the test data from your question in a vector of VEdge (A=0...E=4):

std::vector<VEdge> edges {
    { 2100, 0, 2, 1 },
    { 2101, 1, 1, 2 },
    { 2102, 1, 3, 1 },
    { 2103, 1, 4, 2 },
    { 2104, 2, 1, 7 },
    { 2105, 2, 3, 3 },
    { 2106, 3, 4, 1 },
    { 2107, 4, 0, 1 },
    { 2108, 4, 1, 1 },
};

test_dijkstra test(edges);

The constructor has a little bit of complication to find the number of vertices from just the edges. I used Boost Range algorithms to find the maximum vertex node id and pass that:

test_dijkstra::test_dijkstra(std::vector<VEdge> edges) {
    using namespace boost::adaptors;

    size_t max_node;

    boost::partial_sort_copy(
            edges | transformed([](VEdge const &e) -> size_t { return std::max(e.source, e.target); }),
            boost::make_iterator_range(&max_node, &max_node + 1),
            std::greater<size_t>());

    auto e = edges | transformed([](VEdge const &ve) { return std::make_pair(ve.source, ve.target); });
    kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}

Note how edges.begin() can be passed: it is not "forced to be a an array of edge_property type". An iterator will be fine.

Now the dijkstra needs to get the weight_map argument because it's no longer the default internal property:

void test_dijkstra::run_dijkstra() {

    weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);

    vertex_descriptor kS    = vertex(0, kGraph);
    kP                      = std::vector<vertex_descriptor>(num_vertices(kGraph));
    kD                      = std::vector<int>(num_vertices(kGraph));

    dijkstra_shortest_paths(
        kGraph, kS,
            predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
        .distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
        .weight_map(kWeightMap));
}

For this sample, I translated A to 0 as the starting vertex. The result path is exactly the same as for the original²

enter image description here

Full Program

Live On Coliru

#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <fstream>
#include <iostream>

struct VEdge {
    int id;
    int source, target;
    double weight;
    // custom variables here
};

class test_dijkstra {
    using graph_t           = boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, VEdge>;
    using vertex_descriptor = boost::graph_traits<graph_t>::vertex_descriptor;
    using edge_descriptor   = boost::graph_traits<graph_t>::edge_descriptor;
    using weight_map_t      = boost::property_map<graph_t, double VEdge::*>::type;

  public:
    test_dijkstra(std::vector<VEdge>);
    ~test_dijkstra() {}

    void run_dijkstra();

    void print_path();
    void generate_dot_file();

  private:
    graph_t kGraph;

    std::vector<int> kD;
    std::vector<vertex_descriptor> kP;
};

test_dijkstra::test_dijkstra(std::vector<VEdge> edges) {
    using namespace boost::adaptors;

    size_t max_node;

    boost::partial_sort_copy(
            edges | transformed([](VEdge const &e) -> size_t { return std::max(e.source, e.target); }),
            boost::make_iterator_range(&max_node, &max_node + 1),
            std::greater<size_t>());

    auto e = edges | transformed([](VEdge const &ve) { return std::make_pair(ve.source, ve.target); });
    kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}

void test_dijkstra::run_dijkstra() {

    weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);

    vertex_descriptor kS    = vertex(0, kGraph);
    kP                      = std::vector<vertex_descriptor>(num_vertices(kGraph));
    kD                      = std::vector<int>(num_vertices(kGraph));

    dijkstra_shortest_paths(
        kGraph, kS,
            predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
           .distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
           .weight_map(kWeightMap));
}

void test_dijkstra::print_path() {
    std::cout << "distances and parents:" << std::endl;
    boost::graph_traits<graph_t>::vertex_iterator vi, vend;

    for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
        std::cout << "distance(" << *vi << ") = " << kD[*vi] << ", ";
        std::cout << "parent(" << *vi << ") = " << kP[*vi] << "\n";
    }
}

void test_dijkstra::generate_dot_file() {
    weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);

    std::ofstream dot_file("figs/dijkstra-eg.dot");

    dot_file << "digraph D {\n"
             << "  rankdir=LR\n"
             << "  size=\"4,3\"\n"
             << "  ratio=\"fill\"\n"
             << "  edge[style=\"bold\"]\n"
             << "  node[shape=\"circle\"]\n";

    boost::graph_traits<graph_t>::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
        boost::graph_traits<graph_t>::edge_descriptor e = *ei;
        boost::graph_traits<graph_t>::vertex_descriptor u = source(e, kGraph), v = target(e, kGraph);
        dot_file << u << " -> " << v << "[label=\"" << get(kWeightMap, e) << "\"";

        if (kP[v] == u)
            dot_file << ", color=\"black\"";
        else
            dot_file << ", color=\"grey\"";
        dot_file << "]";
    }
    dot_file << "}";
}

int main() {
    std::vector<VEdge> edges {
        { 2100, 0, 2, 1 },
        { 2101, 1, 1, 2 },
        { 2102, 1, 3, 1 },
        { 2103, 1, 4, 2 },
        { 2104, 2, 1, 7 },
        { 2105, 2, 3, 3 },
        { 2106, 3, 4, 1 },
        { 2107, 4, 0, 1 },
        { 2108, 4, 1, 1 },
    };

    test_dijkstra test(edges);
    test.run_dijkstra();

    test.print_path();
    test.generate_dot_file();
}

2. Using VEdge*

If you insist on using the pointers in the properties a few things become more complicated:

  • you'll need to manage the lifetime of the elements
  • you can't use the double VEdge::* weight_map_t. Instead, you'll need to adapt a custom propertymap for this:

    auto kWeightMap = boost::make_transform_value_property_map(
                [](VEdge* ve) { return ve->weight; },
                boost::get(boost::edge_bundle, kGraph)
            );
    
  • On the bright side, you can use the short-hand indexer notation to evaluate edge properties from an edge_descriptor as shown in generate_dot_file():

    dot_file << u << " -> " << v << "[label=\"" << kGraph[e]->weight << "\"";
    
  • Of course this approach avoids copying the VEdge objects into the bundle, so it could be more efficient

Without further ado (and without bothering about the memory leaks):

Live On Coliru

#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>

#include <boost/property_map/transform_value_property_map.hpp>

#include <fstream>
#include <iostream>

struct VEdge {
    int id;
    int source, target;
    double weight;
    // custom variables here
};

class test_dijkstra {
    using graph_t           = boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, VEdge*>;
    using vertex_descriptor = boost::graph_traits<graph_t>::vertex_descriptor;
    using edge_descriptor   = boost::graph_traits<graph_t>::edge_descriptor;

  public:
    test_dijkstra(std::vector<VEdge*>);
    ~test_dijkstra() {}

    void run_dijkstra();

    void print_path();
    void generate_dot_file();

  private:
    graph_t kGraph;

    std::vector<int> kD;
    std::vector<vertex_descriptor> kP;
};

test_dijkstra::test_dijkstra(std::vector<VEdge*> edges) {
    using namespace boost::adaptors;

    size_t max_node;

    boost::partial_sort_copy(
            edges | transformed([](VEdge const* e) -> size_t { return std::max(e->source, e->target); }),
            boost::make_iterator_range(&max_node, &max_node + 1),
            std::greater<size_t>());

    auto e = edges | transformed([](VEdge const *ve) { return std::make_pair(ve->source, ve->target); });
    kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}

void test_dijkstra::run_dijkstra() {

    auto kWeightMap = boost::make_transform_value_property_map(
                [](VEdge* ve) { return ve->weight; },
                boost::get(boost::edge_bundle, kGraph)
            );

    vertex_descriptor kS    = vertex(0, kGraph);
    kP                      = std::vector<vertex_descriptor>(num_vertices(kGraph));
    kD                      = std::vector<int>(num_vertices(kGraph));

    dijkstra_shortest_paths(
        kGraph, kS,
            predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
           .distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
           .weight_map(kWeightMap));
}

void test_dijkstra::print_path() {
    std::cout << "distances and parents:" << std::endl;
    boost::graph_traits<graph_t>::vertex_iterator vi, vend;

    for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
        std::cout << "distance(" << *vi << ") = " << kD[*vi] << ", ";
        std::cout << "parent(" << *vi << ") = " << kP[*vi] << "\n";
    }
}

void test_dijkstra::generate_dot_file() {

    std::ofstream dot_file("figs/dijkstra-eg.dot");

    dot_file << "digraph D {\n"
             << "  rankdir=LR\n"
             << "  size=\"4,3\"\n"
             << "  ratio=\"fill\"\n"
             << "  edge[style=\"bold\"]\n"
             << "  node[shape=\"circle\"]\n";

    boost::graph_traits<graph_t>::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
        boost::graph_traits<graph_t>::edge_descriptor e = *ei;
        boost::graph_traits<graph_t>::vertex_descriptor u = source(e, kGraph), v = target(e, kGraph);
        dot_file << u << " -> " << v << "[label=\"" << kGraph[e]->weight << "\"";

        if (kP[v] == u)
            dot_file << ", color=\"black\"";
        else
            dot_file << ", color=\"grey\"";
        dot_file << "]";
    }
    dot_file << "}";
}

int main() {
    std::vector<VEdge*> edges {
        new VEdge { 2100, 0, 2, 1 },
        new VEdge { 2101, 1, 1, 2 },
        new VEdge { 2102, 1, 3, 1 },
        new VEdge { 2103, 1, 4, 2 },
        new VEdge { 2104, 2, 1, 7 },
        new VEdge { 2105, 2, 3, 3 },
        new VEdge { 2106, 3, 4, 1 },
        new VEdge { 2107, 4, 0, 1 },
        new VEdge { 2108, 4, 1, 1 },
    };

    test_dijkstra test(edges);
    test.run_dijkstra();

    test.print_path();
    test.generate_dot_file();
}

¹ after swatting silly typos

² self-contained Live On Coliru

like image 114
sehe Avatar answered Sep 21 '22 10:09

sehe