wjakob / nanobind

nanobind: tiny and efficient C++/Python bindings
BSD 3-Clause "New" or "Revised" License
2.14k stars 161 forks source link

[BUG]: Classes Using MatrixXd Attributes and in Initializer List Have Type Errors #518

Closed turnersr closed 2 months ago

turnersr commented 2 months ago

Problem description

Hello,

Really excited to try nanobind. I am attempting to create class using an Eigen Matrix and Vector. Oddly enough, using only vectors work but not matrices.

This works

struct Example {
    std::string name;
    double age;
    Eigen::VectorXd V;

    Example(const std::string &name, double age, const Eigen::VectorXd& V) : name(name), age(age), V(V) { }
};

But this fails:

struct ExampleTwo {
    std::string name;
    double age;
    Eigen::MatrixXd M;

    ExampleTwo(const std::string &name, double age, const Eigen::MatrixXd& M) : name(name), age(age), M(M) { }
};

Example with more code is below. Thank you for your help.

Reproducible example code

This example will work: 

struct Example {
    std::string name;
    double age;
    Eigen::VectorXd V;

    Example(const std::string &name, double age, const Eigen::VectorXd& V) : name(name), age(age), V(V) { }
};

When using this Python code:

def test_vector_with_class():
    V = np.array([1, 2, 3], dtype=np.float64)

    test_example = m.Example("Wow", 2.0, V)

    print(test_example.V)

If however a matrix is used, then this will fail when being invoked from Python:

struct ExampleTwo {
    std::string name;
    double age;
    Eigen::MatrixXd M;

    ExampleTwo(const std::string &name, double age, const Eigen::MatrixXd& M) : name(name), age(age), M(M) { }
};

Here is how I try to create the object, same as before but now with a matrix:

    def test_matrix_with_class():
        M = np.array([[1, 2, 3]], dtype=np.float64)

>       test_example = m.ExampleTwo("Wow", 2.0, M)
E       TypeError: __init__(): incompatible function arguments. The following argument types are supported:
E           1. __init__(self, arg0: str, arg1: float, arg2: numpy.ndarray[dtype=float64, shape=(*, *), order='F'], /) -> None
E       
E       Invoked with types: nanobind_example.nanobind_example_ext.ExampleTwo, str, float, ndarray

Ultimately, my aim is to create class that can hold many different vectors and matrices and then be able to have a list of them:

struct ExampleThree {
    std::string name;
    double age;
    Eigen::MatrixXd M;
    Eigen::VectorXd V;

    ExampleThree(const std::string &name, double age, const Eigen::MatrixXd& M, const Eigen::VectorXd& V) : name(name), age(age), M(M), V(V) { }
};

struct ExampleList {

    std::vector<std::unique_ptr<ExampleThree>> example_list; 
};

Here is how I made the binding:

     nb::class_<Example>(m, "Example")
       .def(nb::init<const std::string &, double, const Eigen::VectorXd&>())
       .def_rw("name", &Example::name)
       .def_rw("age", &Example::age)
       .def_rw("V", &Example::V);

     nb::class_<ExampleTwo>(m, "ExampleTwo")
       .def(nb::init<const std::string &, double, const Eigen::MatrixXd&>())
       .def_rw("name", &ExampleTwo::name)
       .def_rw("age", &ExampleTwo::age)
       .def_rw("M", &ExampleTwo::M);
wjakob commented 2 months ago

Passing vectors and matrices via Eigen are tested as part of the nanobind test suite, and there this works just fine. Could you create a PR that adds a method to the test suite to get this to fail in a way that is easily reproducible for me? I will close this issue for now.