pybind / pybind11

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

[BUG] Callbacks accepting Eigen::Array of custom types #2905

Open ianhbell opened 3 years ago

ianhbell commented 3 years ago

Issue description

I have a callback method that used to (about two years ago) accept an Eigen::Array of a custom type that could be either integer or double (the independent variable in an optimization problem). Now that code no longer works, and I get errors like:

RuntimeError: NumPy type info missing for struct CEGO::numberish

I've narrowed the problem down to callbacks that take Eigen::Array of non POD types, and tried to add the necessary type definitions, but whatever I do I cannot get it to work like it used to. The library is basically toast. Oddly, the binder instance of the library still works fine, which runs older versions of pybind11/Eigen/python, so the regression has happened since then.

Maybe this should never have worked since Eigen doesn't like heterogeneous types, but it did used to work nonetheless.

Reproducible example code

// My custom double/int type (recommendations are welcomed)
struct numberish {
        enum types { EMPTY, INT, DOUBLE } type;
        numberish() { u.d = std::numeric_limits<double>::max(); type = EMPTY; }
        numberish(const int &value) { u.i = value; type = INT; }
        numberish(const long unsigned int& value) { u.i = value; type = INT; }
        numberish(const double &value) { u.d = value; type = DOUBLE; }
        union id {
            double d;
            int i;
        } u;
        ...
};

// The new DTYPE definitions I tried to add
PYBIND11_NUMPY_DTYPE(CEGO::numberish::id, i, d);
PYBIND11_NUMPY_DTYPE(CEGO::numberish, type, u);

// The exposed method
template <typename T> using EArray = Eigen::Array<T, Eigen::Dynamic, 1, 0, Eigen::Dynamic, 1>;
m.def("indexer", [](std::function<CEGO::numberish(const EArray<CEGO::numberish> &)> &f){ 
        EArray<CEGO::numberish> a(10); a = 3.9; return f(a); 
    });
EricCousineau-TRI commented 3 years ago

Any chance you have concrete git ref's of failing pybind11 versions? (if the reproduction code requires other versions of Eigen and/or NumPy, then this may not be a pybind11 issue?)

EricCousineau-TRI commented 3 years ago

But yeah, FWIW, dunno if I'd love the workflow this entails... Why do type-switching inside dtype rather than at broader level (changing array(dtype))? (are you doing symbolic-like stuff?)

Can you show intended call site? (mayhaps there is a better solution?)

ianhbell commented 3 years ago

I am doing optimization with problems where the set of independent variables can be discrete or continuous, which is why I need something like a std::variant type accepting integers or double precision. I started writing that library before C++17 was widely available so I used unions. There is nothing magical about my numerical type, and I would be happy to replace its implementation with something a bit smarter. But I don't think Eigen likes heterogeneous types in general, which is why pybind11 barfs when it hits them. Why it worked before is actually not clear to me. I might need a copy into an Eigen::Array inside the function to allow the interface to not use Eigen::Array, but that copy would get invoked A LOT so the computational penalty would be rather serious.

allanleal commented 3 years ago

Hi @ianhbell . Did you find a solution for this? I had a similar issue yesterday in my library because of the use of Eigen vectors/arrays of autodiff numbers, and googling for RuntimeError: NumPy type info missing for struct brought me to this page. My solution was:

  1. Export such Eigen vectors/array types of custom numbers to Python (see here)
  2. When pybind11/eigen.h is included, I would use PYBIND11_MAKE_OPAQUE to register these Eigen types not to be converted to numpy arrays on the fly (see here)

Not sure if this could help you though.

ianhbell commented 3 years ago

Nope, never did. But I also wonder whether if I were to just switch from union to std::variant whether all my troubles would disappear. Just haven't had the time to get around to that yet.