pybind / pybind11

Seamless operability between C++11 and Python
https://pybind11.readthedocs.io/
Other
15.39k stars 2.08k forks source link

Nested STL container as a return value #1600

Closed semin-park closed 5 years ago

semin-park commented 5 years ago

Issue description

I'm trying to return a nested STL container from a member function.

My function signature is: State reset();

My using statements look like this:

using Action = std::vector<int>;
using ID = std::vector<Action>;

using Point = std::array<long, 2>;
using NewDiagonals = std::set<Point>;

using Board = xt::pytensor<int, 3>;

// Meta contains ID, list of all Actions (for all players), list of all NewDiagonals (for all players) and the set of index of remaining pieces for each player
using Meta = std::tuple<ID, std::vector<std::set<Action>>, std::vector<NewDiagonals>, std::vector<std::set<int>>>;

using State = std::tuple<Board, Meta>;

So basically the State reset(); function that I'm concerned with is returning a State, which is a tuple of Board and Meta. Board is simply a pytensor (from xtensor-python) and can be converted to a numpy array with no problem. Meta on the other hand, is a tuple of ID, vector<set<Action>>, vector<NewDiagonals>, and vector<set<int>>, where ID is a history (list) of actions taken, vector<set<Action>> is supposed to be the set of actions available for each player (it's a multiplayer board game). I think you get the idea.

The thing is, at the root level, there shouldn't be anything that should not be able to be converted to python, given that I have included #include "pybind11/stl.h".

But when I compile the library and try to call it in python, I get this error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Unable to convert function return value to a Python type! The signature was
    (self: Environment.Blokus) -> Tuple[numpy.ndarray[int32], Tuple[List[List[int]], List[Set[List[int]]], List[Set[List[int[2]]]], List[Set[int]]]]

Reproducible example code

#include <vector>
#include <tuple>
#include <map>
#include <array>
#include <set>

#include "pybind11/pybind11.h"            // Pybind11 import to define Python bindings
#include "pybind11/stl.h"
#define FORCE_IMPORT_ARRAY                // numpy C api loading
#include "xtensor-python/pytensor.hpp"     // Numpy bindings

namespace py = pybind11;

using Action = std::vector<int>;
using ID = std::vector<Action>;

using Point = std::array<long, 2>;
using NewDiagonals = std::set<Point>;

// State == {Board, Meta}
using Board = xt::pytensor<int, 3>;

// Meta contains ID, list of all Actions (for all players), list of all NewDiagonals (for all players)
using Meta = std::tuple<ID, std::vector<std::set<Action>>, std::vector<NewDiagonals>, std::vector<std::set<int>>>;

// for env.step()
using State = std::tuple<Board, Meta>;

struct Foo
{
    Foo(){}

    State foo() {
        Board board = xt::zeros<int>({2,3,4});

        ID root_id{{0}};
        std::vector<std::set<Action>> actions_all(2);
        std::vector<NewDiagonals> first_pos{
            NewDiagonals{Point{0,0}},
        };
        std::vector<std::set<int>> remaining(2);

        Meta meta = std::tie(root_id, actions_all, first_pos, remaining);
        return std::tie(board, meta);
    }
};

PYBIND11_MODULE(example, m)
{
    xt::import_numpy();

    py::class_<Foo>(m, "Foo")
    .def(py::init<>())
    .def("foo", &Foo::foo);
}
semin-park commented 5 years ago

The problem was that c++ set (implemented as a tree) is converted to python set (hash set). And I was putting vectors into sets, which in python is inserting lists into sets -- which is not possible