提问者:小点点

如何读取GraphML文件用自定义顶点属性来Boost Graph?


我正试图创建一个机器人关节配置空间的增强图。为此,我使用自定义顶点属性。

struct VertexProperties {
    std::vector<double> joint_angles;
    
    VertexProperties() : joint_angles(3){}
    
};

我使用write_graphML保存了图形,实现如下

std::string BoostGraph::vertexJointAngles(Vertex& v){
   
    std::ostringstream sstream;
    std::vector<double> jointAngles(graph[v].joint_angles);

    for (std::size_t i = 0; i < jointAngles.size(); i++){
                 sstream  << jointAngles[i];
                 if(i != jointAngles.size()-1) sstream << ',';
        }

    return sstream.str();
}


```
void BoostGraph::printGraphML(std::ostream &out){

    boost::function_property_map<std::function<std::string(Vertex)>, Vertex> jointAngles([this](Vertex v)
                                                                                        {
                                                                                            return vertexJointAngles(v);
                                                                                        });

                                                                                      });   
    boost::dynamic_properties dp;

    dp.property("Joint_Angles", jointAngles);
    dp.property("edge_weight", boost::get(&EdgeProperties::weight, graph));
    
    boost::write_graphml(out, graph, dp);
}

现在我需要在另一个BoostGraph类实例中再次读取该图,以进行查询和A*搜索。如何将在graphml中保存为字符串的关节角度转换为顶点属性?

我尝试了以下实现(我刚开始使用Boost),但它不起作用。

std::vector<double> BoostGraph::readJointAngles(std::string joint_angle_string){
    std::stringstream ss;

    ss << joint_angle_string;

    std::vector<double> joint_angle;
    double number;
    while (ss >> number)
        joint_angle.push_back(number);

    return joint_angle;
}

void BoostGraph::readGraphGraphML(std::istream& file){
    boost::dynamic_properties dp;

    boost::function_property_map<std::function<std::vector<double>(std::string)>, Vertex> jointAngles([this](std::string v)
                                                                                        {
                                                                                            return readJointAngles(v);
                                                                                        });
    dp.property("Joint_Angles", jointAngles );                                                                                   
    dp.property("edge_weight", boost::get(&EdgeProperties::weight, graph));
    boost::read_graphml(file, graph, dp);
}

共1个答案

匿名用户

那里的起点很好。干得不错。

因此,我将立即跳到痛点:

可写属性映射必须返回建模左值语义学的东西。您的readJointAngles改为假定“过程”转换任务。那不是属性映射。

我的第一个想法是通过将读和写组合到一个代理类中来接近属性映射的概念。代理类可以是<code>VertexProperties</code>本身,但正如我所想象的那样,<code>VertexProperty</code〕具有多个具有相似需求的成员,让我做“纯”的事情,并在这里将其分开:

struct JointAngleWrapper {
    std::vector<double>& _ref;

    friend std::ostream& operator<<(std::ostream& os, JointAngleWrapper wrapper) {
        for (auto sep = ""; auto el : wrapper._ref)
            os << std::exchange(sep, ",") << el;
        return os;
    }
    friend std::istream& operator>>(std::istream& is, JointAngleWrapper wrapper) {
        std::vector<double> tmp;

        while (is >> tmp.emplace_back())
            if (char comma{}; !(is >> comma) || comma != ',')
                break;

        wrapper._ref = std::move(tmp);

        is.clear();
        return is;
    }
};

现在你可以替换函数属性映射:

    auto jointAngles = boost::make_function_property_map<Vertex>(
        [ja = get(&VertexProperties::joint_angles, graph)](Vertex v) {
            return JointAngleWrapper{ja[v]};
        });
    dp.property("Joint_Angles", jointAngles);

输出仍然相同。事实上,我们有一个更轻量级的属性映射抽象,它更优雅地满足了需求:

    auto jointAngles = make_transform_value_property_map(
        [](std::vector<double>& ja) { return JointAngleWrapper{ja}; },
        get(&VertexProperties::joint_angles, graph));

然而,function_property_map仍然不是左值属性映射。这很可悲。

原来我忘记了这个障碍。我之前已经写过这个相当核心的问题read_graphml(或者dynamic_properties,真的):

    < li>Boost::Graph:如何使用自定义顶点类(链接到其他相关答案)导入graphviz

我在那里采取的方法是创建我自己的ReadWrite属性图。这是明智的,因为它基本上合并到我们上面已经拥有的内容中(JointAngleWrapper)。

template <typename Prop> struct JA {
    Prop inner;
    JA(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(JA map, key_type const& key) {
        std::ostringstream oss;
        for (auto sep = ""; auto el : get(map.inner, key))
            oss << std::exchange(sep, ",") << el;
        return oss.str();
    }
    friend void put(JA map, key_type const& key, value_type const& value) {
        auto& v = get(map.inner, key);
        v.clear();

        std::istringstream iss(value);
        while (iss >> v.emplace_back())
            if (char comma{}; !(iss >> comma) || comma != ',')
                break;
    }
};

现在您可以成功地使用它了:

struct BoostGraph {
    Graph graph;

    auto properties() {
        boost::dynamic_properties dp;
        dp.property("Joint_Angles", JA{get(&VertexProperties::joint_angles, graph)});
        dp.property("edge_weight", get(&EdgeProperties::weight, graph));
        return dp;
    }

    void printGraphML(std::ostream& out) {
        write_graphml(out, graph, properties());
    }

    void readGraphGraphML(std::istream& file) {
        auto dp = properties();
        read_graphml(file, graph, dp);
    }
};

它成功地往返于“随机”图形:

#include <boost/graph/random.hpp>
#include <random>

int main() {
    std::stringstream file; // fake file in memory
    {
        BoostGraph g;
        std::mt19937 prng{42};
        generate_random_graph(g.graph, 5, 2, prng);

        for (auto v : boost::make_iterator_range(vertices(g.graph)))
            for (auto& angle : g.graph[v].joint_angles)
                angle = std::uniform_real_distribution<double>(-M_PI, +M_PI)(prng);

        g.printGraphML(file);
    }

    {
        BoostGraph roundtrip;
        roundtrip.readGraphGraphML(file);
        roundtrip.printGraphML(std::cout);
    }
}

打印

<?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="node" attr.name="Joint_Angles" attr.type="string" />
  <key id="key1" for="edge" attr.name="edge_weight" attr.type="double" />
  <graph id="G" edgedefault="directed" parse.nodeids="free" parse.edgeids="canonical" parse.order="nodesfirst">
    <node id="n0">
      <data key="key0">1.75735,0.608528,-0.340343</data>
    </node>
    <node id="n1">
      <data key="key0">-2.51343,-0.256047,-1.04484</data>
    </node>
    <node id="n2">
      <data key="key0">-2.24393,0.94806,-2.78715</data>
    </node>
    <node id="n3">
      <data key="key0">1.39486,2.75551,-3.1367</data>
    </node>
    <node id="n4">
      <data key="key0">3.09266,0.738158,0.701538</data>
    </node>
    <edge id="e0" source="n1" target="n3">
      <data key="key1">0</data>
    </edge>
    <edge id="e1" source="n4" target="n0">
      <data key="key1">0</data>
    </edge>
  </graph>
</graphml>

住在科里鲁

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

struct VertexProperties {
    std::vector<double> joint_angles{0, 0, 0};
};
struct EdgeProperties {
    double weight;
};

using Graph  = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
                                    VertexProperties, EdgeProperties>;
using Vertex = Graph::vertex_descriptor;
using Edge   = Graph::edge_descriptor;

template <typename Prop> struct JA {
    Prop inner;
    JA(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(JA map, key_type const& key) {
        std::ostringstream oss;
        for (auto sep = ""; auto el : get(map.inner, key))
            oss << std::exchange(sep, ",") << el;
        return oss.str();
    }
    friend void put(JA map, key_type const& key, value_type const& value) {
        auto& v = get(map.inner, key);
        v.clear();

        std::istringstream iss(value);
        while (iss >> v.emplace_back())
            if (char comma{}; !(iss >> comma) || comma != ',')
                break;
    }
};

struct BoostGraph {
    Graph graph;

    auto properties() {
        boost::dynamic_properties dp;
        dp.property("Joint_Angles", JA{get(&VertexProperties::joint_angles, graph)});
        dp.property("edge_weight", get(&EdgeProperties::weight, graph));
        return dp;
    }

    void printGraphML(std::ostream& out) {
        write_graphml(out, graph, properties());
    }

    void readGraphGraphML(std::istream& file) {
        auto dp = properties();
        read_graphml(file, graph, dp);
    }
};

#include <boost/graph/random.hpp>
#include <random>

int main() {
    std::stringstream file; // fake file in memory
    {
        BoostGraph g;
        std::mt19937 prng{42};
        generate_random_graph(g.graph, 5, 2, prng);

        for (auto v : boost::make_iterator_range(vertices(g.graph)))
            for (auto& angle : g.graph[v].joint_angles)
                angle = std::uniform_real_distribution<double>(-M_PI, +M_PI)(prng);

        g.printGraphML(file);
    }

    {
        BoostGraph roundtrip;
        roundtrip.readGraphGraphML(file);
        roundtrip.printGraphML(std::cout);
    }
}