pybind11 icon indicating copy to clipboard operation
pybind11 copied to clipboard

[BUG]: Eigen::SparseMatrix -> unexpected overload-resolution / template-instantiation

Open sschnug opened this issue 1 year ago • 0 comments

Required prerequisites

  • [X] Make sure you've read the documentation. Your issue may be addressed there.
  • [X] Search the issue tracker and Discussions to verify that this hasn't already been reported. +1 or comment there if it has.
  • [ ] Consider asking first in the Gitter chat room or in a Discussion.

What version (or hash if on master) of pybind11 are you using?

723307283ed7dc03c901fba1da1127c7b16a4a0d

Problem description

I'm trying to wrap dense as well as sparse-matrices (py -> c++): specifically

  • Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic>
  • Eigen::SparseMatrix<T, Eigen::RowMajor>
  • Eigen::SparseMatrix<T, Eigen::ColMajor>

Having tried two approaches, one being based on EigenBase<Derived>, the other being based on splitting the target functions into a dense and a sparse variant (as shown in example).

In both cases, something unexpected is happening to the type in the sparse-case while the dense case works as expected.

tests/test_basic.py
 Dense matrix @ python
[[1. 2. 3. 4.]
[5. 6. 7. 8.]]
float32

Sparse matrix @ python
  (0, 0)        1.0
  (0, 1)        2.0
  (0, 2)        3.0
  (0, 3)        4.0
  (1, 0)        5.0
  (1, 1)        6.0
  (1, 2)        7.0
  (1, 3)        8.0
float32

CALL instantiation -> with dense => c++ receives type?
Dense overload
float
CALL instantiation -> with sparse => c++ receives type?
Sparse overload
bool ??? WHY ???

Reproducible example: https://github.com/sschnug/cmake_example/tree/eigen_sparse_matrix_overload_issue_example

Reproducible example code

See: https://github.com/sschnug/cmake_example/tree/eigen_sparse_matrix_overload_issue_example

But basically:

// OKAY BEHAVIOUR -> dense
template <typename ScalarT>
static void take_matrix(const Eigen::Array<ScalarT, Eigen::Dynamic, Eigen::Dynamic> matrix_in) {
  std::cout << "Dense overload" << std::endl;

  if constexpr(std::is_same_v<ScalarT, float>) {
    std::cout << "float" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, double>) {
    std::cout << "double" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, bool>) {
    std::cout << "bool" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, int8_t>) {
    std::cout << "int8_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, int16_t>) {
    std::cout << "int16_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, int32_t>) {
    std::cout << "int32_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, int64_t>) {
    std::cout << "int64_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, uint8_t>) {
    std::cout << "uint8_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, uint16_t>) {
    std::cout << "uint16_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, uint32_t>) {
    std::cout << "uint32_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, uint64_t>) {
    std::cout << "uint64_t" << std::endl;
  }
  else 
    std::cout << "some other type" << std::endl;
}

// NOT OKAY BEHAVIOUR -> sparse
template<typename ScalarT, int Options>
static void take_matrix(const Eigen::SparseMatrix<ScalarT, Options> matrix_in) {
  std::cout << "Sparse overload" << std::endl;

  if constexpr(std::is_same_v<ScalarT, float>) {
    std::cout << "float" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, double>) {
    std::cout << "double" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, bool>) {
    std::cout << "bool" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, int8_t>) {
    std::cout << "int8_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, int16_t>) {
    std::cout << "int16_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, int32_t>) {
    std::cout << "int32_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, int64_t>) {
    std::cout << "int64_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, uint8_t>) {
    std::cout << "uint8_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, uint16_t>) {
    std::cout << "uint16_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, uint32_t>) {
    std::cout << "uint32_t" << std::endl;
  }
  else if constexpr(std::is_same_v<ScalarT, uint64_t>) {
    std::cout << "uint64_t" << std::endl;
  }
  else 
    std::cout << "some other type" << std::endl;
}

PYBIND11_MODULE(cmake_example, m) {

    // dense
    // -----

    m.def("take_matrix", &take_matrix<bool>, pybind11::arg("matrix_in").noconvert());
    m.def("take_matrix", &take_matrix<float>, pybind11::arg("matrix_in").noconvert());
    m.def("take_matrix", &take_matrix<double>, pybind11::arg("matrix_in").noconvert());

    // sparse
    // ------

    m.def("take_matrix", &take_matrix<bool, Eigen::ColMajor>, pybind11::arg("matrix_in").noconvert());
    m.def("take_matrix", &take_matrix<bool, Eigen::RowMajor>, pybind11::arg("matrix_in").noconvert());

    m.def("take_matrix", &take_matrix<float, Eigen::ColMajor>, pybind11::arg("matrix_in").noconvert());
    m.def("take_matrix", &take_matrix<float, Eigen::RowMajor>, pybind11::arg("matrix_in").noconvert());

    m.def("take_matrix", &take_matrix<double, Eigen::ColMajor>, pybind11::arg("matrix_in").noconvert());
    m.def("take_matrix", &take_matrix<double, Eigen::RowMajor>, pybind11::arg("matrix_in").noconvert());

Is this a regression? Put the last known working version here if it is.

Not a regression

sschnug avatar Aug 02 '24 11:08 sschnug