2

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.

Joseph Schmidt
  • 119
  • 1
  • 10

1 Answers1

5

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.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • added individual member property map access demos using various constructs – sehe Apr 15 '21 at 15:53
  • I believe the struct road_details_t and typedef property is an alternative to BOOST_INSTALL_PROPERTY, but I've tried both ways and neither worked for me. Editing to include that as a comment. – Joseph Schmidt Apr 15 '21 at 16:44
  • It appears that the example is attaching attributes to the vertices, road_info belongs to edges – Joseph Schmidt Apr 15 '21 at 16:50
  • Firstly, no that's not an alternative. Secondly, I was following your own example typedef that used `road_info` as a vertex property., so either your question or _"road_info belongs to edges"_ was kind of a lie :) Just use it for the edges then: http://coliru.stacked-crooked.com/a/012508ccac9468ee (99% same code) – sehe Apr 15 '21 at 17:21
  • Of course I wouldn't let you go without the [elusive third approach](http://coliru.stacked-crooked.com/a/f720f4f1d0afb1c9). See the edited answer under "For Masochists" – sehe Apr 15 '21 at 17:34
  • Wait, where did I set road_info as a vertex property? Regarding the "alternative", from https://www.boost.org/doc/libs/1_75_0/libs/graph/doc/using_adjacency_list.html: "You can also use enum's instead of struct's to create tag types". Regarding accessing the edge, can this also be done without an iterator using the edge(vertex, vertex, graph) function? At some point I need to be able to have a function take in 2 vertices and pull out whether or not there's a pothole. – Joseph Schmidt Apr 15 '21 at 18:39
  • "Wait, where did I set road_info as a vertex property?:" - in your [`Graph` typedef](https://stackoverflow.com/questions/67110765/using-boost-how-can-i-put-get-custom-edge-properties-as-a-struct/67111757?noredirect=1#question:~:text=typedef%20boost%3A%3Aadjacency_list%3Cboost%3A%3AlistS%2C,RoadDetailsProperty), like I said. No biggie, mistakes happen. – sehe Apr 15 '21 at 18:46
  • _"At some point I need to be able to have a function take in 2 vertices and pull out whether or not there's a pothole"_ Yes. It's a general purpose library and you can do these. Depending on the chosen graph model it will be more or less efficient. See also [concepts](https://www.boost.org/doc/libs/1_75_0/libs/graph/doc/graph_concepts.html#boost-common-heading-doc-spacer:~:text=AdjacencyMatrix%20refines%20Graph,edge(u%2C%20v%2C%20g)) – sehe Apr 15 '21 at 18:47
  • About "alternative to" there also seems to be a budding misunderstanding. But I responded to your comment where you said _"I believe the struct road_details_t and typedef property **is an alternative to `BOOST_INSTALL_PROPERTY`**"_. Quod non. Yeah, typedefs are possible (enums **are** types, and their members **are** values of that type, so it's not actually an alternative; it's merely the same with different syntax). – sehe Apr 15 '21 at 18:50
  • The only alternative to using `BOOST_INSTALL_PROPERTY` is [by specializing `boost::property_kind<>` trait manually](http://coliru.stacked-crooked.com/a/4a5c87c9e45e4d65) for your tag. That's not only being masochist but also actively going past the documented API and using library implementation details (honestly, you'll be fine, but it's not supported). – sehe Apr 15 '21 at 18:59
  • "in your Graph typedef, like I said. No biggie, mistakes happen", of course, but I'm not able to see the mistake, which means I still have a misunderstanding... Do you mean this part: ... is RoadDetailsProperty somehow being attached to Vertex instead of Edge? If so, why? – Joseph Schmidt Apr 17 '21 at 19:33
  • Yes. Because [it's the VertexProperties type parameter](https://www.boost.org/doc/libs/1_76_0/libs/graph/doc/adjacency_list.html). If you look at [my code](https://stackoverflow.com/questions/67110765/using-boost-how-can-i-put-get-custom-edge-properties-as-a-struct/67111757?noredirect=1#comment118628404_67111757), you can easily see the difference. – sehe Apr 18 '21 at 01:10