c++boostboost-graph

Problem with basic Boost Graph read_graphml use case


I cannot for the life of me understand how to setup read_graphml correctly.

Ommiting obvious file IO, so far I have:

struct NodeProperties {
  std::string d{};
  std::string e{};
  std::string f{};
};
struct EdgeProperties {};
struct GraphProperties {
  std::string a{};
  std::string b{};
  std::string c{};
};

usingDiGraph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, NodeProperties, EdgeProperties, GraphProperties>;
DiGraph myTree;

std::map<std::string,std::string> attribute_name2name;
//attribute_name2name.insert( make_pair(std::string("a"), std::string("a") ) ); // doesnt help
boost::associative_property_map<std::map<std::string,std::string>> graphname_map( attribute_name2name );

boost::dynamic_properties treeProperties;
treeProperties.property( "a", graphname_map );

try {
  read_graphml(graphmlFile, tree, treeProperties);
}
catch ( std::exception &ex ) {
  std::cerr << "Exception caught from read_graphml: " << ex.what() << "\n";
}
boost::print_graph(tree, "tree");

The file looks more or less like this (sorry if i made a typo, i need to manually write it because reasons):

<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
  <key id="key0" for="graph" attr.name="a" attr.type="string" />
  <key id="key1" for="graph" attr.name="b" attr.type="string" />
  <key id="key2" for="graph" attr.name="c" attr.type="string" />
  <key id="key3" for="node" attr.name="d" attr.type="string" />
  <key id="key4" for="node" attr.name="e" attr.type="string" />
  <key id="key5" for="node" attr.name="f" attr.type="string" />
  <graph id="G" edgefault="directed" parse.nodeis="canonical" parse.edgeids="canonical" parse.order="nodesfirst">
    <data key="key0">foo</data>
    <data key="key1">foo</data>
    <data key="key2">foo</data>
      <node id="n0">
        <data key="key3">bar</data>
        <data key="key4">bar</data>
        <data key="key5">bar</data>
      </node>
      <node id="n1">
        <data key="key3">bar</data>
        <data key="key4">bar</data>
        <data key="key5">bar</data>
      </node>
      <node id="n2">
        <data key="key3">bar</data>
        <data key="key4">bar</data>
        <data key="key5">bar</data>
      </node>
      <edge id="e0" source="n0" target="n1"> </edge>
      <edge id="e0" source="n0" target="n2"> </edge>
    </graph>
</graphml>

read_graphml throws exception:

Property not found: a

HOWEVER, if the fourth argument, optional argument of read_graphml function is set to 'G', no exception happens - however print tree doesn't print anything, not even "tree"

Note that I do not need the code to be generic. I KNOW what the node and graph properties are, and I know they wont change. I can statically bake them into the source.

Note that I did not use ignore_other_properties because I want to read them all - and anyway here it crashes on first property in the file. Related questions dont help me or contain no answer, but may be useful:

how to read graph-domain attributes with boost::read_graphml?

Boost Read_graphml doesn't read xml properly it gives all the vertices but they are empty

I tried doing it like here but the compiler cannot resolve boost::get(&GraphProperties::a,tree), example on Coliru:

https://coliru.stacked-crooked.com/a/ef8a74489dc1f53b

EDIT: My hunch is that the way of going about it presented on Coliru is probably a better way - since many people asking on SO seem to use it - but that is only if the compiler error can be fixed.


Solution

  • You are confusing property maps with maps. Instead of a single map (this will only give you 1 element per key anyways), you want a "mapping" (technically, a PropertyMap) that maps descriptors to corresponding values.

    I'd expect this:

    boost::dynamic_properties dp;
    dp.property("d", get(&NodeProperties::d, myTree));
    dp.property("e", get(&NodeProperties::e, myTree));
    dp.property("f", get(&NodeProperties::f, myTree));
    

    Now, the graph properties are a different beast. You need to map a reference to the the "surrogate key" of type DiGraph*:

    auto graph_prop = [&g](auto member) {
        return boost::make_function_property_map<DiGraph*>(
            [&g, member](DiGraph*) -> decltype(auto) { return g[boost::graph_bundle].*member; });
    };
    
    dp.property("a", graph_prop(&GraphProperties::a));
    dp.property("b", graph_prop(&GraphProperties::b));
    dp.property("c", graph_prop(&GraphProperties::c));
    

    There might be a better way to map those, but I'm not aware of it.

    Demo Time

    Live On Coliru

    #include <boost/graph/adjacency_list.hpp>
    #include <boost/graph/graph_utility.hpp>
    #include <boost/graph/graphml.hpp>
    #include <boost/property_map/function_property_map.hpp>
    #include <fstream>
    #include <iomanip>
    #include <iostream>
    
    struct NodeProperties {
        std::string d, e, f;
    
        friend std::ostream& operator<<(std::ostream& os, NodeProperties const& np) {
            return os << "{d:" << quoted(np.d) << ", e:" << quoted(np.e) << ", f:" << quoted(np.f) << "}";
        }
    };
    struct EdgeProperties {};
    struct GraphProperties {
        std::string a, b, c;
    
        friend std::ostream& operator<<(std::ostream& os, GraphProperties const& gp) {
            return os << "{a:" << quoted(gp.a) << ", b:" << quoted(gp.b) << ", c:" << quoted(gp.c) << "}";
        }
    };
    
    using DiGraph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, NodeProperties,
                                          EdgeProperties, GraphProperties>;
    int main() try {
        DiGraph g;
        /*
         *auto    n0 = add_vertex({"bar", "bar", "bar"}, g);
         *auto    n1 = add_vertex({"baz", "baz", "baz"}, g);
         *auto    n2 = add_vertex({"qux", "qux", "qux"}, g);
         *add_edge(n0, n1, g);
         *add_edge(n0, n2, g);
         */
    
        boost::dynamic_properties dp;
        dp.property("d", get(&NodeProperties::d, g));
        dp.property("e", get(&NodeProperties::e, g));
        dp.property("f", get(&NodeProperties::f, g));
    
        auto graph_prop = [&g](auto member) {
            return boost::make_function_property_map<DiGraph*>(
                [&g, member](DiGraph*) -> decltype(auto) { return g[boost::graph_bundle].*member; });
        };
    
        dp.property("a", graph_prop(&GraphProperties::a));
        dp.property("b", graph_prop(&GraphProperties::b));
        dp.property("c", graph_prop(&GraphProperties::c));
    
        {
            std::ifstream ifs("input.xml");
            read_graphml(ifs, g, dp);
        }
    
        std::cout << "Read graph with " << num_vertices(g) << " vertices and " << num_edges(g) << " edges\n";
        std::cout << "Graph properties: " << g[boost::graph_bundle] << "\n";
    
        print_graph(g, get(&NodeProperties::d, g));
        print_graph(g, get(boost::vertex_bundle, g));
    
        {
            std::ofstream ofs("output.xml");
            write_graphml(ofs, g, dp);
        }
    
    } catch (std::exception const& ex) {
        std::cerr << "Exception caught from read_graphml: " << ex.what() << "\n";
    }
    

    Output

    Read graph with 3 vertices and 2 edges
    Graph properties: {a:"graph_a", b:"graph_b", c:"graph_c"}
    bar_d --> baz_d qux_d 
    baz_d --> 
    qux_d --> 
    {d:"bar_d", e:"bar_e", f:"bar_f"} --> {d:"baz_d", e:"baz_e", f:"baz_f"} {d:"qux_d", e:"qux_e", f:"qux_f"} 
    {d:"baz_d", e:"baz_e", f:"baz_f"} --> 
    {d:"qux_d", e:"qux_e", f:"qux_f"} --> 
    

    And roundtripped output.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
      <key id="key0" for="graph" attr.name="a" attr.type="string" />
      <key id="key1" for="graph" attr.name="b" attr.type="string" />
      <key id="key2" for="graph" attr.name="c" attr.type="string" />
      <key id="key3" for="node" attr.name="d" attr.type="string" />
      <key id="key4" for="node" attr.name="e" attr.type="string" />
      <key id="key5" for="node" attr.name="f" attr.type="string" />
      <graph id="G" edgedefault="directed" parse.nodeids="free" parse.edgeids="canonical" parse.order="nodesfirst">
       <data key="key0">graph_a</data>
       <data key="key1">graph_b</data>
       <data key="key2">graph_c</data>
        <node id="n0">
          <data key="key3">bar_d</data>
          <data key="key4">bar_e</data>
          <data key="key5">bar_f</data>
        </node>
        <node id="n1">
          <data key="key3">baz_d</data>
          <data key="key4">baz_e</data>
          <data key="key5">baz_f</data>
        </node>
        <node id="n2">
          <data key="key3">qux_d</data>
          <data key="key4">qux_e</data>
          <data key="key5">qux_f</data>
        </node>
        <edge id="e0" source="n0" target="n1">
        </edge>
        <edge id="e1" source="n0" target="n2">
        </edge>
      </graph>
    </graphml>