Using Boost, how can I put/get custom edge properties as a struct?

869 views Asked by At

I've been over the documentation (https://www.boost.org/doc/libs/1_75_0/libs/graph/doc/using_adjacency_list.html) here and several stack overflow pages for two hours and am not making any forward progress here at all. I have a graph where edges have both distance and number of potholes (vehicle breaks after hitting 3 potholes), so I was thinking I would use a custom struct and set edge properties.

Placed this above the class declaration:

struct road_details_t {
  typedef boost::edge_property_tag kind;
};
struct road_info {
  unsigned miles;
  bool pothole;
};
typedef boost::property<road_details_t, struct road_info> RoadDetailsProperty;

EDIT: I believe this is an alternative to the above and have not had luck with it either:

namespace boost {
    enum road_details_t { road_details };
    BOOST_INSTALL_PROPERTY(edge, road_details);
}

The adjacency graph inside the class declaration:

typedef boost::adjacency_list<boost::listS,       // store edges in lists
                              boost::vecS,        // store vertices in a vector
                              boost::undirectedS
                              RoadDetailsProperty
                              >
    Graph;
typedef boost::graph_traits<Graph> GraphTraits;
typedef GraphTraits::vertex_descriptor Vertex;
typedef GraphTraits::edge_descriptor Edge;

Graph roadMap;

Later in a function to add roads:

void City::addRoad(Intersection intersection1, Intersection intersection2, unsigned time, bool pothole) 
{
    unsigned nV = num_vertices(roadMap);
    while (intersection1 >= nV)
        nV = add_vertex(roadMap)+1;
    while (intersection2 >= nV)
        nV = add_vertex(roadMap)+1;
    add_edge(intersection1, intersection2, roadMap);

So now I'm trying to put/get the road info, which has multiple errors and I've tried a lot of variations:

// get the edge
std::pair<Edge, bool> edg = edge(intersection1, intersection2, roadMap);

// get the property map for road details. ERROR: no matching function call to get()
typename property_map<Graph, road_details_t>::type
  roadDetailMap = get(road_details_t(), roadMap);

// create struct from arguments passed in
struct road_info info = {time, pothole};

// attempt to add to roadDetailMap
put(roadDetailMap, edg, info);
// roadDetailMap[edg] = info; // also not working

// attempt to fetch information
cout << get(roadDetailMap, edg).miles << endl;
// cout << roadDetailMap[edg].miles << endl; // also not working

By commenting out various lines I've found the issue seems to be in how I'm getting the property map, but looking at examples online I feel like I'm doing the exact same thing they are.

The goal is ultimately just to figure out how to add the road information and how to get it later, so an entirely different approach is acceptible, what I have here is just what I've tried (unsuccessfully) but that I felt had the most promising results.

1

There are 1 answers

12
sehe On

You can do as you suggest, which requires registering a property tag using BOOST_INSTALL_PROPERTY (see example/examples/edge_property.cpp for an example). Relevant snippet:

namespace boost {
  enum edge_flow_t { edge_flow };
  enum edge_capacity_t { edge_capacity };

  BOOST_INSTALL_PROPERTY(edge, flow);
  BOOST_INSTALL_PROPERTY(edge, capacity);
}

Now you can use your new property tag in the definition of properties just as you would one of the builtin tags.

  typedef property<capacity_t, int> Cap;
  typedef property<flow_t, int, Cap> EdgeProperty;
  typedef adjacency_list<vecS, vecS, no_property, EdgeProperty> Graph;

However, since your use case is so frequent, there's already a built-in tag for any user-define struct: vertex_bundle_t and edge_bundle_t:

Bundled Properties

Here's a sample using that. Documentation: https://www.boost.org/doc/libs/1_75_0/libs/graph/doc/bundles.html

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>

struct road_info {
    unsigned miles;
    bool     pothole;
};

using Graph  = boost::adjacency_list<boost::listS, boost::vecS,
                                    boost::undirectedS, road_info>;
using Vertex = Graph::vertex_descriptor;
using Edge   = Graph::edge_descriptor;

int main() {
    Graph roadMap;

    Vertex road1     = add_vertex(road_info{15, false}, roadMap);
    Vertex road2     = add_vertex(road_info{3, true}, roadMap);
    /*Vertex road3 =*/ add_vertex(road_info{27, false}, roadMap);

    add_edge(road1, road2, roadMap);

    print_graph(roadMap);

    auto bmap = get(boost::vertex_bundle, roadMap);

    for (Vertex v : boost::make_iterator_range(vertices(roadMap))) {
        road_info& bundle = bmap[v];
        // or even easier
        // road_info& info = roadMap[v];
        auto& [miles, pothole] = roadMap[v]; // c++17

        std::cout << "Vertex #" << v << " " << miles << " miles, "
                  << "pothole:" << std::boolalpha << pothole << "\n";
    }

    // individual maps
    auto miles_map = get(&road_info::miles, roadMap);
    auto poth_map = get(&road_info::pothole, roadMap);

    for (Vertex v : boost::make_iterator_range(vertices(roadMap))) {
        std::cout << "Vertex #" << v << " " << miles_map[v] << " miles, "
                  << "pothole:" << std::boolalpha << poth_map[v] << "\n";

        put(poth_map, v, false); // reset pothole
    }
}

Prints

0 <--> 1 
1 <--> 0 
2 <--> 
Vertex #0 15 miles, pothole:false
Vertex #1 3 miles, pothole:true
Vertex #2 27 miles, pothole:false
Vertex #0 15 miles, pothole:false
Vertex #1 3 miles, pothole:true
Vertex #2 27 miles, pothole:false

UPDATE: Edge Bundles

To the comments, making it an edge bundle is superficial modifications:

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>

struct road_info {
    unsigned miles;
    bool     pothole;
};

using Graph =
    boost::adjacency_list<boost::listS, boost::vecS, boost::undirectedS,
                          boost::no_property, road_info>;
using Vertex = Graph::vertex_descriptor;
using Edge   = Graph::edge_descriptor;

int main() {
    Graph roadMap(3);

    add_edge(0, 1, road_info{15, false}, roadMap);
    add_edge(1, 2, road_info{3, true}, roadMap);

    print_graph(roadMap);

    auto bmap = get(boost::edge_bundle, roadMap);

    for (Edge e : boost::make_iterator_range(edges(roadMap))) {
        road_info& bundle = bmap[e];
        // or even easier
        // road_info& info = roadMap[e];
        auto& [miles, pothole] = roadMap[e]; // c++17

        std::cout << "Edge " << e << " " << miles << " miles, "
                  << "pothole:" << std::boolalpha << pothole << "\n";
    }

    // individual maps
    auto miles_map = get(&road_info::miles, roadMap);
    auto poth_map = get(&road_info::pothole, roadMap);

    for (Edge e : boost::make_iterator_range(edges(roadMap))) {
        std::cout << "Edge " << e << " " << miles_map[e] << " miles, "
                  << "pothole:" << std::boolalpha << poth_map[e] << "\n";

        put(poth_map, e, false); // reset pothole
    }
}

Printing:

0 <--> 1 
1 <--> 0 2 
2 <--> 1 
Edge (0,1) 15 miles, pothole:false
Edge (1,2) 3 miles, pothole:true
Edge (0,1) 15 miles, pothole:false
Edge (1,2) 3 miles, pothole:true

For Masochists

If you insist you can of course install a property tag. It will be uncomfortable (and potentially inefficient) to get to the individual members, but you do you:

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/property_map/transform_value_property_map.hpp>

struct road_info {
    unsigned miles;
    bool     pothole;
};

namespace boost {
    struct edge_road_info_t {};
    BOOST_INSTALL_PROPERTY(edge, road_info);
    static inline constexpr edge_road_info_t road_info{};
}

using Graph =
    boost::adjacency_list<boost::listS, boost::vecS, boost::undirectedS,
                          boost::no_property, 
                          boost::property<boost::edge_road_info_t, road_info> >;
using Vertex = Graph::vertex_descriptor;
using Edge   = Graph::edge_descriptor;

int main() {
    Graph roadMap(3);

    add_edge(0, 1, road_info{15, false}, roadMap);
    add_edge(1, 2, road_info{3, true}, roadMap);

    print_graph(roadMap);

    auto info_map = get(boost::road_info, roadMap);

    for (Edge e : boost::make_iterator_range(edges(roadMap))) {
        road_info& info = info_map[e];
        // or even easier
        // road_info& info = roadMap[e];
        auto& [miles, pothole] = info; // c++17

        std::cout << "Edge " << e << " " << miles << " miles, "
                  << "pothole:" << std::boolalpha << pothole << "\n";
    }

    // individual maps
    auto miles_map = boost::make_transform_value_property_map(
        std::mem_fn(&road_info::miles), info_map);
    auto poth_map = boost::make_transform_value_property_map(
        std::mem_fn(&road_info::pothole), info_map);

    for (Edge e : boost::make_iterator_range(edges(roadMap))) {
        std::cout << "Edge " << e << " " << miles_map[e] << " miles, "
                  << "pothole:" << std::boolalpha << poth_map[e] << "\n";

        put(poth_map, e, false); // reset pothole
    }
}

Again printing the same output.