1

I have this graphviz input:

graph G {
0[p="(30, 3, 2)"];
1[p="(29, 3, 2)"];
2[p="(30, 2, 2)"];
3[p="(30, 3, 3)"];
4[p="(30, 2, 3)"];
5[p="(29, 3, 3)"];
6[p="(29, 2, 3)"];
0--1;
2--0;
3--4;
5--3;
6--5;
5--1;
3--0;
4--6;
2--4;
}

The types are:

struct Vertex
{
    glm::vec3 p = {};
    // ...
};

typedef
boost::adjacency_list<
    boost::setS,
    boost::vecS,
    boost::undirectedS,
    Vertex,
    Edge>
Graph;

How can I use boost::read_graphviz and set it up to correctly adapt the p property in graphviz to the p field of the struct Vertex? I've tried to use boost::dynamic_properties with dp.property("p", boost::get(&Vertex::p, g)); but it doesn't work because the types don't match (and maybe because is for write_graphviz instead).

sehe
  • 374,641
  • 47
  • 450
  • 633
TesX
  • 931
  • 1
  • 9
  • 29

1 Answers1

1

Yeah, you're asking for heroics. The dynamic_properties facility really sort-of assumes you will be accessing lvalue properties in objects, not transformed values.

I've run into this before:

Solving It

The library is thoroughly generic. Property Maps are highly generic and do NOT assume lvalue-ness, which can be seen from the concept hierarchy that distinguishes between ReadWritePropertyMap and LvaluePropertyMap.

So you can actually "just" write your own property map adaptor:

namespace Adapt {
    template <typename Prop> struct Vec3 {
        Prop inner;
        Vec3(Prop map) : inner(map) { }

        // traits
        using value_type = std::string;
        using reference  = std::string;
        using key_type   = typename boost::property_traits<Prop>::key_type;
        using category   = boost::read_write_property_map_tag;

        friend std::string get(Vec3 adapt, key_type const& key);
        friend void put(Vec3 adapt, key_type const& key, value_type const& value);
    };
}

I'll quickly fill in some sensible implementation for get and put based on your expected text serialized format. I won't explain the details, and you can write it in any way you deem appropriate:

friend std::string get(Vec3 adapt, key_type const& key) {
    auto const& v = get(adapt.inner, key);
    std::ostringstream oss;
    oss << "(" << v.x << "," << v.y << "," << v.z << ")";
    return oss.str();
}

friend void put(Vec3 adapt, key_type const& key, std::string const& value) {
    using namespace boost::spirit::x3;

    float x,y,z;
    auto attr = std::tie(x,y,z);
    phrase_parse( //
        begin(value), end(value),
        '(' > double_ > ',' > double_ > ',' > double_ > ')' > eoi,
        space, attr);

    put(adapt.inner, key, glm::vec3{x,y,z});
}

Full Demo

Now you can use the adapted property map:

Graph g;
auto id = boost::get(&Vertex::id, g);
auto p  = Adapt::Vec3{boost::get(&Vertex::p, g)};

boost::dynamic_properties dp;
dp.property("node_id", id);
dp.property("p", p);

And if we roundtrip the graph:

{
    std::ifstream ifs("input.txt", std::ios::binary);
    boost::read_graphviz(ifs, g, dp);
}

boost::write_graphviz_dp(std::cout, g, dp);

We can see that input is preserved. Sadly no online compilers can support both Boost and GLM at the time of writing, so you'll have to run the example yourself (see https://godbolt.org/z/xMKhz9G8e, https://wandbox.org/permlink/RkulvhWxcRnl1RbC etc).

Full listing for that purpose:

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <glm/glm.hpp>
#include <iostream>
#include <iomanip>

struct Vertex {
    glm::vec3 p = {};
    int id;
};

struct Edge { };

using Graph = boost::adjacency_list<boost::setS, boost::vecS,
                                    boost::undirectedS, Vertex, Edge>;
using VD = Graph::vertex_descriptor;
using ED = Graph::edge_descriptor;

namespace Adapt {
    template <typename Prop> struct Vec3 {
        Prop inner;
        Vec3(Prop map) : inner(map) { }

        // traits
        using value_type = std::string;
        using reference  = std::string;
        using key_type   = typename boost::property_traits<Prop>::key_type;
        using category   = boost::read_write_property_map_tag;

        friend std::string get(Vec3 adapt, key_type const& key) {
            auto const& v = get(adapt.inner, key);
            std::ostringstream oss;
            oss << "(" << v.x << "," << v.y << "," << v.z << ")";
            return oss.str();
        }

        friend void put(Vec3 adapt, key_type const& key, std::string const& value) {
            using namespace boost::spirit::x3;

            float x,y,z;
            auto attr = std::tie(x,y,z);
            phrase_parse( //
                begin(value), end(value),
                '(' > double_ > ',' > double_ > ',' > double_ > ')' > eoi,
                space, attr);

            put(adapt.inner, key, glm::vec3{x,y,z});
        }
    };
}

int main()
{
    Graph g;
    auto id = boost::get(&Vertex::id, g);
    auto p  = Adapt::Vec3{boost::get(&Vertex::p, g)};

    boost::dynamic_properties dp;
    dp.property("node_id", id);
    dp.property("p", p);

    {
        std::istringstream iss(R"~(
            graph G {
            0[p="(30, 3, 2)"]; 1[p="(29, 3, 2)"]; 2[p="(30, 2, 2)"]; 3[p="(30, 3, 3)"];
            4[p="(30, 2, 3)"]; 5[p="(29, 3, 3)"]; 6[p="(29, 2, 3)"];
            0--1; 2--0; 3--4; 5--3; 6--5; 5--1; 3--0; 4--6; 2--4; })~");
        boost::read_graphviz(iss, g, dp);
    }

    boost::write_graphviz_dp(std::cout, g, dp);
}

Prints

graph G {
0 [p="(30,3,2)"];
1 [p="(29,3,2)"];
2 [p="(30,2,2)"];
3 [p="(30,3,3)"];
4 [p="(30,2,3)"];
5 [p="(29,3,3)"];
6 [p="(29,2,3)"];
0--1 ;
2--0 ;
3--4 ;
5--3 ;
6--5 ;
5--1 ;
3--0 ;
4--6 ;
2--4 ;
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • It worked, thank you!! I've tried to implement an adaptor but I couldn't do it... Sometimes, how boost is wired up is very intricate and I've found the docs not really explaining it very well. I need to understand better what `boost::get(&Vertex::p, g)` does and more... Thanks again! ;) – TesX Jul 29 '21 at 16:00