Repository: stack-of-tasks/eigenpy (language: python)

Issue:
Add support of Eigen::Tensor



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 29629aece..867d72f10 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -20,6 +20,8 @@ jobs:
       - run: |
           sudo apt-get update
           sudo apt-get install cmake libboost-all-dev libeigen3-dev python*-numpy python*-dev
+          echo $(sudo apt list --installed)
+          echo $(g++ --version)
       - run: cmake -DPYTHON_EXECUTABLE=$(which python${{ matrix.python }}) .
       - run: make -j2
       - run: make test
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5aef07dd6..f667f1d85 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -5,7 +5,7 @@ repos:
     rev: v15.0.7
     hooks:
     -   id: clang-format
-        args: ['--style={BasedOnStyle: Google, SortIncludes: false}']
+        args: ['--style={BasedOnStyle: Google, SortIncludes: false, Standard: Cpp03}']
 -   repo: https://github.com/pre-commit/pre-commit-hooks
     rev: v4.4.0
     hooks:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b4b12e377..d1187c1bf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -67,6 +67,7 @@ string(REPLACE "-pedantic" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
 
 # If needed, fix CMake policy for APPLE systems
 apply_default_apple_configuration()
+check_minimal_cxx_standard(11 ENFORCE)
 
 if(WIN32)
   set(LINK copy_if_different)
@@ -164,6 +165,7 @@ set(${PROJECT_NAME}_HEADERS
     include/eigenpy/std-vector.hpp
     include/eigenpy/pickle-vector.hpp
     include/eigenpy/stride.hpp
+    include/eigenpy/tensor/eigen-from-python.hpp
     include/eigenpy/swig.hpp
     include/eigenpy/version.hpp)
 
diff --git a/include/eigenpy/alignment.hpp b/include/eigenpy/alignment.hpp
index 4bca14d36..d37af7006 100644
--- a/include/eigenpy/alignment.hpp
+++ b/include/eigenpy/alignment.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023, INRIA
+ * Copyright 2023 INRIA
  */
 
 #ifndef __eigenpy_alignment_hpp__
@@ -67,6 +67,23 @@ struct referent_storage<
       typename eigenpy::aligned_storage<referent_size<T &>::value>::type type;
 };
 
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename Scalar, int Rank, int Options, typename IndexType>
+struct referent_storage<Eigen::Tensor<Scalar, Rank, Options, IndexType> &> {
+  typedef Eigen::Tensor<Scalar, Rank, Options, IndexType> T;
+  typedef
+      typename eigenpy::aligned_storage<referent_size<T &>::value>::type type;
+};
+
+template <typename Scalar, int Rank, int Options, typename IndexType>
+struct referent_storage<
+    const Eigen::Tensor<Scalar, Rank, Options, IndexType> &> {
+  typedef Eigen::Tensor<Scalar, Rank, Options, IndexType> T;
+  typedef
+      typename eigenpy::aligned_storage<referent_size<T &>::value>::type type;
+};
+#endif
+
 template <typename Scalar, int Options>
 struct referent_storage<Eigen::Quaternion<Scalar, Options> &> {
   typedef Eigen::Quaternion<Scalar, Options> T;
diff --git a/include/eigenpy/details.hpp b/include/eigenpy/details.hpp
index 12be4b094..a71c59f47 100644
--- a/include/eigenpy/details.hpp
+++ b/include/eigenpy/details.hpp
@@ -17,24 +17,49 @@
 #include "eigenpy/scalar-conversion.hpp"
 
 namespace eigenpy {
-template <typename MatType, typename EigenEquivalentType>
-EIGENPY_DEPRECATED void enableEigenPySpecific() {
-  enableEigenPySpecific<MatType>();
-}
+
+template <typename EigenType,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct expose_eigen_type_impl;
 
 template <typename MatType>
-void enableEigenPySpecific() {
-  if (check_registration<MatType>()) return;
+struct expose_eigen_type_impl<MatType, Eigen::MatrixBase<MatType> > {
+  static void run() {
+    if (check_registration<MatType>()) return;
 
-  // to-python
-  EigenToPyConverter<MatType>::registration();
+    // to-python
+    EigenToPyConverter<MatType>::registration();
 #if EIGEN_VERSION_AT_LEAST(3, 2, 0)
-  EigenToPyConverter<Eigen::Ref<MatType> >::registration();
-  EigenToPyConverter<const Eigen::Ref<const MatType> >::registration();
+    EigenToPyConverter<Eigen::Ref<MatType> >::registration();
+    EigenToPyConverter<const Eigen::Ref<const MatType> >::registration();
+#endif
+
+    // from-python
+    EigenFromPyConverter<MatType>::registration();
+  }
+};
+
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename TensorType>
+struct expose_eigen_type_impl<TensorType, Eigen::TensorBase<TensorType> > {
+  static void run() {
+    if (check_registration<TensorType>()) return;
+
+    // to-python
+    EigenToPyConverter<TensorType>::registration();
+    EigenToPyConverter<Eigen::TensorRef<TensorType> >::registration();
+    EigenToPyConverter<
+        const Eigen::TensorRef<const TensorType> >::registration();
+
+    // from-python
+    EigenFromPyConverter<TensorType>::registration();
+  }
+};
 #endif
 
-  // from-python
-  EigenFromPyConverter<MatType>::registration();
+template <typename MatType>
+void enableEigenPySpecific() {
+  expose_eigen_type_impl<MatType>::run();
 }
 
 }  // namespace eigenpy
diff --git a/include/eigenpy/eigen-allocator.hpp b/include/eigenpy/eigen-allocator.hpp
index 589159d01..7c31c56f4 100644
--- a/include/eigenpy/eigen-allocator.hpp
+++ b/include/eigenpy/eigen-allocator.hpp
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2014-2020 CNRS INRIA
+// Copyright (c) 2014-2023 CNRS INRIA
 //
 
 #ifndef __eigenpy_eigen_allocator_hpp__
@@ -70,31 +70,114 @@ struct init_matrix_or_array<MatType, true> {
   }
 };
 
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename Tensor>
+struct init_tensor {
+  static Tensor *run(PyArrayObject *pyArray, void *storage = NULL) {
+    enum { Rank = Tensor::NumDimensions };
+    assert(PyArray_NDIM(pyArray) == Rank);
+    typedef typename Tensor::Index Index;
+
+    Eigen::array<Index, Rank> dimensions;
+    for (int k = 0; k < PyArray_NDIM(pyArray); ++k)
+      dimensions[k] = PyArray_DIMS(pyArray)[k];
+
+    if (storage)
+      return new (storage) Tensor(dimensions);
+    else
+      return new Tensor(dimensions);
+  }
+};
+#endif
+
 template <typename MatType>
-bool check_swap(PyArrayObject *pyArray, const Eigen::MatrixBase<MatType> &mat) {
-  if (PyArray_NDIM(pyArray) == 0) return false;
-  if (mat.rows() == PyArray_DIMS(pyArray)[0])
-    return false;
-  else
-    return true;
+struct check_swap_impl_matrix;
+
+template <typename EigenType,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct check_swap_impl;
+
+template <typename MatType>
+struct check_swap_impl<MatType, Eigen::MatrixBase<MatType> >
+    : check_swap_impl_matrix<MatType> {};
+
+template <typename MatType>
+struct check_swap_impl_matrix {
+  static bool run(PyArrayObject *pyArray,
+                  const Eigen::MatrixBase<MatType> &mat) {
+    if (PyArray_NDIM(pyArray) == 0) return false;
+    if (mat.rows() == PyArray_DIMS(pyArray)[0])
+      return false;
+    else
+      return true;
+  }
+};
+
+template <typename EigenType>
+bool check_swap(PyArrayObject *pyArray, const EigenType &mat) {
+  return check_swap_impl<EigenType>::run(pyArray, mat);
 }
 
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename TensorType>
+struct check_swap_impl_tensor {
+  static bool run(PyArrayObject * /*pyArray*/, const TensorType & /*tensor*/) {
+    return false;
+  }
+};
+
+template <typename TensorType>
+struct check_swap_impl<TensorType, Eigen::TensorBase<TensorType> >
+    : check_swap_impl_tensor<TensorType> {};
+#endif
+
+// template <typename MatType>
+// struct cast_impl_matrix;
+//
+// template <typename EigenType,
+//           typename BaseType = typename get_eigen_base_type<EigenType>::type>
+// struct cast_impl;
+//
+// template <typename MatType>
+// struct cast_impl<MatType, Eigen::MatrixBase<MatType> >
+//     : cast_impl_matrix<MatType> {};
+//
+// template <typename MatType>
+// struct cast_impl_matrix
+//{
+//   template <typename NewScalar, typename MatrixIn, typename MatrixOut>
+//   static void run(const Eigen::MatrixBase<MatrixIn> &input,
+//                   const Eigen::MatrixBase<MatrixOut> &dest) {
+//     dest.const_cast_derived() = input.template cast<NewScalar>();
+//   }
+// };
+
 template <typename Scalar, typename NewScalar,
+          template <typename D> class EigenBase = Eigen::MatrixBase,
           bool cast_is_valid = FromTypeToType<Scalar, NewScalar>::value>
-struct cast_matrix_or_array {
+struct cast {
   template <typename MatrixIn, typename MatrixOut>
   static void run(const Eigen::MatrixBase<MatrixIn> &input,
                   const Eigen::MatrixBase<MatrixOut> &dest) {
-    MatrixOut &dest_ = const_cast<MatrixOut &>(dest.derived());
-    dest_ = input.template cast<NewScalar>();
+    dest.const_cast_derived() = input.template cast<NewScalar>();
   }
 };
 
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
 template <typename Scalar, typename NewScalar>
-struct cast_matrix_or_array<Scalar, NewScalar, false> {
+struct cast<Scalar, NewScalar, Eigen::TensorRef, true> {
+  template <typename TensorIn, typename TensorOut>
+  static void run(const TensorIn &input, TensorOut &dest) {
+    dest = input.template cast<NewScalar>();
+  }
+};
+#endif
+
+template <typename Scalar, typename NewScalar,
+          template <typename D> class EigenBase>
+struct cast<Scalar, NewScalar, EigenBase, false> {
   template <typename MatrixIn, typename MatrixOut>
-  static void run(const Eigen::MatrixBase<MatrixIn> & /*input*/,
-                  const Eigen::MatrixBase<MatrixOut> & /*dest*/) {
+  static void run(const MatrixIn /*input*/, const MatrixOut /*dest*/) {
     // do nothing
     assert(false && "Must never happened");
   }
@@ -104,19 +187,37 @@ struct cast_matrix_or_array<Scalar, NewScalar, false> {
 
 #define EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, Scalar, NewScalar, \
                                                   pyArray, mat)               \
-  details::cast_matrix_or_array<Scalar, NewScalar>::run(                      \
+  details::cast<Scalar, NewScalar>::run(                                      \
       NumpyMap<MatType, Scalar>::map(pyArray,                                 \
                                      details::check_swap(pyArray, mat)),      \
       mat)
 
 #define EIGENPY_CAST_FROM_EIGEN_MATRIX_TO_PYARRAY(MatType, Scalar, NewScalar, \
                                                   mat, pyArray)               \
-  details::cast_matrix_or_array<Scalar, NewScalar>::run(                      \
+  details::cast<Scalar, NewScalar>::run(                                      \
       mat, NumpyMap<MatType, NewScalar>::map(                                 \
                pyArray, details::check_swap(pyArray, mat)))
 
+template <typename EigenType>
+struct EigenAllocator;
+
+template <typename EigenType,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct eigen_allocator_impl;
+
+template <typename MatType>
+struct eigen_allocator_impl_matrix;
+
+template <typename MatType>
+struct eigen_allocator_impl<MatType, Eigen::MatrixBase<MatType> >
+    : eigen_allocator_impl_matrix<MatType> {};
+
+template <typename MatType>
+struct eigen_allocator_impl<const MatType, const Eigen::MatrixBase<MatType> >
+    : eigen_allocator_impl_matrix<const MatType> {};
+
 template <typename MatType>
-struct EigenAllocator {
+struct eigen_allocator_impl_matrix {
   typedef MatType Type;
   typedef typename MatType::Scalar Scalar;
 
@@ -130,8 +231,17 @@ struct EigenAllocator {
     Type *mat_ptr = details::init_matrix_or_array<Type>::run(pyArray, raw_ptr);
     Type &mat = *mat_ptr;
 
+    copy(pyArray, mat);
+  }
+
+  /// \brief Copy Python array into the input matrix mat.
+  template <typename MatrixDerived>
+  static void copy(PyArrayObject *pyArray,
+                   const Eigen::MatrixBase<MatrixDerived> &mat_) {
+    MatrixDerived &mat = mat_.const_cast_derived();
     const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray);
     const int Scalar_type_code = Register::getTypeCode<Scalar>();
+
     if (pyArray_type_code == Scalar_type_code) {
       mat = NumpyMap<MatType, Scalar>::map(
           pyArray, details::check_swap(pyArray, mat));  // avoid useless cast
@@ -185,13 +295,10 @@ struct EigenAllocator {
     const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray);
     const int Scalar_type_code = Register::getTypeCode<Scalar>();
 
-    typedef typename NumpyMap<MatType, Scalar>::EigenMap MapType;
-
     if (pyArray_type_code == Scalar_type_code)  // no cast needed
     {
-      MapType map_pyArray = NumpyMap<MatType, Scalar>::map(
-          pyArray, details::check_swap(pyArray, mat));
-      map_pyArray = mat;
+      NumpyMap<MatType, Scalar>::map(pyArray,
+                                     details::check_swap(pyArray, mat)) = mat;
       return;
     }
 
@@ -234,6 +341,159 @@ struct EigenAllocator {
   }
 };
 
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename TensorType>
+struct eigen_allocator_impl_tensor;
+
+template <typename TensorType>
+struct eigen_allocator_impl<TensorType, Eigen::TensorBase<TensorType> >
+    : eigen_allocator_impl_tensor<TensorType> {};
+
+template <typename TensorType>
+struct eigen_allocator_impl<const TensorType,
+                            const Eigen::TensorBase<TensorType> >
+    : eigen_allocator_impl_tensor<const TensorType> {};
+
+template <typename TensorType>
+struct eigen_allocator_impl_tensor {
+  typedef typename TensorType::Scalar Scalar;
+  static void allocate(
+      PyArrayObject *pyArray,
+      boost::python::converter::rvalue_from_python_storage<TensorType>
+          *storage) {
+    void *raw_ptr = storage->storage.bytes;
+    assert(is_aligned(raw_ptr, EIGENPY_DEFAULT_ALIGN_BYTES) &&
+           "The pointer is not aligned.");
+
+    TensorType *tensor_ptr =
+        details::init_tensor<TensorType>::run(pyArray, raw_ptr);
+    TensorType &tensor = *tensor_ptr;
+
+    copy(pyArray, tensor);
+  }
+
+#define EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, Scalar,         \
+                                                  NewScalar, pyArray, tensor) \
+  {                                                                           \
+    typename NumpyMap<TensorType, Scalar>::EigenMap pyArray_map =             \
+        NumpyMap<TensorType, Scalar>::map(                                    \
+            pyArray, details::check_swap(pyArray, tensor));                   \
+    details::cast<Scalar, NewScalar, Eigen::TensorRef>::run(pyArray_map,      \
+                                                            tensor);          \
+  }
+
+  /// \brief Copy Python array into the input matrix mat.
+  template <typename TensorDerived>
+  static void copy(PyArrayObject *pyArray, TensorDerived &tensor) {
+    const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray);
+    const int Scalar_type_code = Register::getTypeCode<Scalar>();
+
+    if (pyArray_type_code == Scalar_type_code) {
+      tensor = NumpyMap<TensorType, Scalar>::map(
+          pyArray, details::check_swap(pyArray, tensor));  // avoid useless cast
+      return;
+    }
+
+    switch (pyArray_type_code) {
+      case NPY_INT:
+        EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, int, Scalar,
+                                                  pyArray, tensor);
+        break;
+      case NPY_LONG:
+        EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, long, Scalar,
+                                                  pyArray, tensor);
+        break;
+      case NPY_FLOAT:
+        EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, float, Scalar,
+                                                  pyArray, tensor);
+        break;
+      case NPY_CFLOAT:
+        EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(
+            TensorType, std::complex<float>, Scalar, pyArray, tensor);
+        break;
+      case NPY_DOUBLE:
+        EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, double, Scalar,
+                                                  pyArray, tensor);
+        break;
+      case NPY_CDOUBLE:
+        EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(
+            TensorType, std::complex<double>, Scalar, pyArray, tensor);
+        break;
+      case NPY_LONGDOUBLE:
+        EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, long double,
+                                                  Scalar, pyArray, tensor);
+        break;
+      case NPY_CLONGDOUBLE:
+        EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(
+            TensorType, std::complex<long double>, Scalar, pyArray, tensor);
+        break;
+      default:
+        throw Exception("You asked for a conversion which is not implemented.");
+    }
+  }
+
+#define EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar,         \
+                                                  NewScalar, tensor, pyArray) \
+  {                                                                           \
+    typename NumpyMap<TensorType, NewScalar>::EigenMap pyArray_map =          \
+        NumpyMap<TensorType, NewScalar>::map(                                 \
+            pyArray, details::check_swap(pyArray, tensor));                   \
+    details::cast<Scalar, NewScalar, Eigen::TensorRef>::run(tensor,           \
+                                                            pyArray_map);     \
+  }
+
+  /// \brief Copy mat into the Python array using Eigen::Map
+  static void copy(const TensorType &tensor, PyArrayObject *pyArray) {
+    const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray);
+    const int Scalar_type_code = Register::getTypeCode<Scalar>();
+
+    if (pyArray_type_code == Scalar_type_code)  // no cast needed
+    {
+      NumpyMap<TensorType, Scalar>::map(
+          pyArray, details::check_swap(pyArray, tensor)) = tensor;
+      return;
+    }
+
+    switch (pyArray_type_code) {
+      case NPY_INT:
+        EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, int,
+                                                  tensor, pyArray);
+        break;
+      case NPY_LONG:
+        EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, long,
+                                                  tensor, pyArray);
+        break;
+      case NPY_FLOAT:
+        EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, float,
+                                                  tensor, pyArray);
+        break;
+      case NPY_CFLOAT:
+        EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(
+            TensorType, Scalar, std::complex<float>, tensor, pyArray);
+        break;
+      case NPY_DOUBLE:
+        EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, double,
+                                                  tensor, pyArray);
+        break;
+      case NPY_CDOUBLE:
+        EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(
+            TensorType, Scalar, std::complex<double>, tensor, pyArray);
+        break;
+      case NPY_LONGDOUBLE:
+        EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar,
+                                                  long double, tensor, pyArray);
+        break;
+      case NPY_CLONGDOUBLE:
+        EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(
+            TensorType, Scalar, std::complex<long double>, tensor, pyArray);
+        break;
+      default:
+        throw Exception("You asked for a conversion which is not implemented.");
+    }
+  }
+};
+#endif
+
 #if EIGEN_VERSION_AT_LEAST(3, 2, 0)
 /// @brief Check if we need to allocate @tparam MatType to convert @param
 /// pyArray.
@@ -253,7 +513,7 @@ inline bool is_arr_layout_compatible_with_mat_type(PyArrayObject *pyArray) {
 }
 
 template <typename MatType, int Options, typename Stride>
-struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > {
+struct eigen_allocator_impl_matrix<Eigen::Ref<MatType, Options, Stride> > {
   typedef Eigen::Ref<MatType, Options, Stride> RefType;
   typedef typename MatType::Scalar Scalar;
 
@@ -296,49 +556,7 @@ struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > {
       new (raw_ptr) StorageType(mat_ref, pyArray, mat_ptr);
 
       RefType &mat = *reinterpret_cast<RefType *>(raw_ptr);
-      if (pyArray_type_code == Scalar_type_code) {
-        mat = NumpyMap<MatType, Scalar>::map(
-            pyArray, details::check_swap(pyArray, mat));  // avoid useless cast
-        return;
-      }
-
-      switch (pyArray_type_code) {
-        case NPY_INT:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, int, Scalar,
-                                                    pyArray, mat);
-          break;
-        case NPY_LONG:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, long, Scalar,
-                                                    pyArray, mat);
-          break;
-        case NPY_FLOAT:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, float, Scalar,
-                                                    pyArray, mat);
-          break;
-        case NPY_CFLOAT:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(
-              MatType, std::complex<float>, Scalar, pyArray, mat);
-          break;
-        case NPY_DOUBLE:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, double, Scalar,
-                                                    pyArray, mat);
-          break;
-        case NPY_CDOUBLE:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(
-              MatType, std::complex<double>, Scalar, pyArray, mat);
-          break;
-        case NPY_LONGDOUBLE:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, long double,
-                                                    Scalar, pyArray, mat);
-          break;
-        case NPY_CLONGDOUBLE:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(
-              MatType, std::complex<long double>, Scalar, pyArray, mat);
-          break;
-        default:
-          throw Exception(
-              "You asked for a conversion which is not implemented.");
-      }
+      EigenAllocator<MatType>::copy(pyArray, mat);
     } else {
       assert(pyArray_type_code == Scalar_type_code);
       typename NumpyMap<MatType, Scalar, Options, NumpyMapStride>::EigenMap
@@ -355,7 +573,8 @@ struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > {
 };
 
 template <typename MatType, int Options, typename Stride>
-struct EigenAllocator<const Eigen::Ref<const MatType, Options, Stride> > {
+struct eigen_allocator_impl_matrix<
+    const Eigen::Ref<const MatType, Options, Stride> > {
   typedef const Eigen::Ref<const MatType, Options, Stride> RefType;
   typedef typename MatType::Scalar Scalar;
 
@@ -399,49 +618,7 @@ struct EigenAllocator<const Eigen::Ref<const MatType, Options, Stride> > {
       new (raw_ptr) StorageType(mat_ref, pyArray, mat_ptr);
 
       MatType &mat = *mat_ptr;
-      if (pyArray_type_code == Scalar_type_code) {
-        mat = NumpyMap<MatType, Scalar>::map(
-            pyArray, details::check_swap(pyArray, mat));  // avoid useless cast
-        return;
-      }
-
-      switch (pyArray_type_code) {
-        case NPY_INT:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, int, Scalar,
-                                                    pyArray, mat);
-          break;
-        case NPY_LONG:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, long, Scalar,
-                                                    pyArray, mat);
-          break;
-        case NPY_FLOAT:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, float, Scalar,
-                                                    pyArray, mat);
-          break;
-        case NPY_CFLOAT:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(
-              MatType, std::complex<float>, Scalar, pyArray, mat);
-          break;
-        case NPY_DOUBLE:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, double, Scalar,
-                                                    pyArray, mat);
-          break;
-        case NPY_CDOUBLE:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(
-              MatType, std::complex<double>, Scalar, pyArray, mat);
-          break;
-        case NPY_LONGDOUBLE:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, long double,
-                                                    Scalar, pyArray, mat);
-          break;
-        case NPY_CLONGDOUBLE:
-          EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(
-              MatType, std::complex<long double>, Scalar, pyArray, mat);
-          break;
-        default:
-          throw Exception(
-              "You asked for a conversion which is not implemented.");
-      }
+      EigenAllocator<MatType>::copy(pyArray, mat);
     } else {
       assert(pyArray_type_code == Scalar_type_code);
       typename NumpyMap<MatType, Scalar, Options, NumpyMapStride>::EigenMap
@@ -457,6 +634,90 @@ struct EigenAllocator<const Eigen::Ref<const MatType, Options, Stride> > {
   }
 };
 #endif
+
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+
+template <typename TensorType, typename TensorRef>
+struct eigen_allocator_impl_tensor_ref;
+
+template <typename TensorType>
+struct eigen_allocator_impl_tensor<Eigen::TensorRef<TensorType> >
+    : eigen_allocator_impl_tensor_ref<TensorType,
+                                      Eigen::TensorRef<TensorType> > {};
+
+template <typename TensorType>
+struct eigen_allocator_impl_tensor<const Eigen::TensorRef<const TensorType> >
+    : eigen_allocator_impl_tensor_ref<
+          const TensorType, const Eigen::TensorRef<const TensorType> > {};
+
+template <typename TensorType, typename RefType>
+struct eigen_allocator_impl_tensor_ref {
+  typedef typename TensorType::Scalar Scalar;
+
+  typedef
+      typename ::boost::python::detail::referent_storage<RefType &>::StorageType
+          StorageType;
+
+  static void allocate(
+      PyArrayObject *pyArray,
+      ::boost::python::converter::rvalue_from_python_storage<RefType>
+          *storage) {
+    //    typedef typename StrideType<
+    //        MatType,
+    //        Eigen::internal::traits<RefType>::StrideType::InnerStrideAtCompileTime,
+    //        Eigen::internal::traits<RefType>::StrideType::
+    //            OuterStrideAtCompileTime>::type NumpyMapStride;
+
+    static const int Options = Eigen::internal::traits<TensorType>::Options;
+
+    bool need_to_allocate = false;
+    const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray);
+    const int Scalar_type_code = Register::getTypeCode<Scalar>();
+    if (pyArray_type_code != Scalar_type_code) need_to_allocate |= true;
+    //    bool incompatible_layout =
+    //        !is_arr_layout_compatible_with_mat_type<MatType>(pyArray);
+    //    need_to_allocate |= incompatible_layout;
+    //    if (Options !=
+    //        Eigen::Unaligned)  // we need to check whether the memory is
+    //        correctly
+    //                           // aligned and composed of a continuous segment
+    //    {
+    //      void *data_ptr = PyArray_DATA(pyArray);
+    //      if (!PyArray_ISONESEGMENT(pyArray) || !is_aligned(data_ptr,
+    //      Options))
+    //        need_to_allocate |= true;
+    //    }
+
+    void *raw_ptr = storage->storage.bytes;
+    if (need_to_allocate) {
+      typedef typename boost::remove_const<TensorType>::type TensorTypeNonConst;
+      TensorTypeNonConst *tensor_ptr;
+      tensor_ptr = details::init_tensor<TensorTypeNonConst>::run(pyArray);
+      RefType tensor_ref(*tensor_ptr);
+
+      new (raw_ptr) StorageType(tensor_ref, pyArray, tensor_ptr);
+
+      TensorTypeNonConst &tensor = *tensor_ptr;
+      EigenAllocator<TensorTypeNonConst>::copy(pyArray, tensor);
+    } else {
+      assert(pyArray_type_code == Scalar_type_code);
+      typename NumpyMap<TensorType, Scalar, Options>::EigenMap numpyMap =
+          NumpyMap<TensorType, Scalar, Options>::map(pyArray);
+      RefType tensor_ref(numpyMap);
+      new (raw_ptr) StorageType(tensor_ref, pyArray);
+    }
+  }
+
+  static void copy(RefType const &ref, PyArrayObject *pyArray) {
+    EigenAllocator<TensorType>::copy(ref, pyArray);
+  }
+};
+
+#endif
+
+template <typename EigenType>
+struct EigenAllocator : eigen_allocator_impl<EigenType> {};
+
 }  // namespace eigenpy
 
 #endif  // __eigenpy_eigen_allocator_hpp__
diff --git a/include/eigenpy/eigen-from-python.hpp b/include/eigenpy/eigen-from-python.hpp
index f03ae268c..f31e3512e 100644
--- a/include/eigenpy/eigen-from-python.hpp
+++ b/include/eigenpy/eigen-from-python.hpp
@@ -12,13 +12,12 @@
 
 namespace eigenpy {
 
-template <typename C>
+template <typename EigenType,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
 struct expected_pytype_for_arg {};
 
-template <typename Scalar, int Rows, int Cols, int Options, int MaxRows,
-          int MaxCols>
-struct expected_pytype_for_arg<
-    Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> > {
+template <typename MatType>
+struct expected_pytype_for_arg<MatType, Eigen::MatrixBase<MatType> > {
   static PyTypeObject const *get_pytype() {
     PyTypeObject const *py_type = eigenpy::getPyArrayType();
     return py_type;
@@ -59,44 +58,43 @@ struct copy_if_non_const<const MatType, true> {
 };
 
 #if EIGEN_VERSION_AT_LEAST(3, 2, 0)
-template <typename MatType, int Options, typename Stride>
-struct referent_storage_eigen_ref;
 
-template <typename MatType, int Options, typename Stride>
+template <typename _RefType>
 struct referent_storage_eigen_ref {
-  typedef Eigen::Ref<MatType, Options, Stride> RefType;
+  typedef _RefType RefType;
+  typedef typename get_eigen_ref_plain_type<RefType>::type PlainObjectType;
   typedef typename ::eigenpy::aligned_storage<
       ::boost::python::detail::referent_size<RefType &>::value>::type
       AlignedStorage;
 
   referent_storage_eigen_ref()
       : pyArray(NULL),
-        mat_ptr(NULL),
+        plain_ptr(NULL),
         ref_ptr(reinterpret_cast<RefType *>(ref_storage.bytes)) {}
 
   referent_storage_eigen_ref(const RefType &ref, PyArrayObject *pyArray,
-                             MatType *mat_ptr = NULL)
+                             PlainObjectType *plain_ptr = NULL)
       : pyArray(pyArray),
-        mat_ptr(mat_ptr),
+        plain_ptr(plain_ptr),
         ref_ptr(reinterpret_cast<RefType *>(ref_storage.bytes)) {
     Py_INCREF(pyArray);
     new (ref_storage.bytes) RefType(ref);
   }
 
   ~referent_storage_eigen_ref() {
-    if (mat_ptr != NULL && PyArray_ISWRITEABLE(pyArray))
-      copy_if_non_const<MatType>::run(*mat_ptr, pyArray);
+    if (plain_ptr != NULL && PyArray_ISWRITEABLE(pyArray))
+      copy_if_non_const<PlainObjectType>::run(*plain_ptr, pyArray);
 
     Py_DECREF(pyArray);
 
-    if (mat_ptr != NULL) mat_ptr->~MatType();
+    if (plain_ptr != NULL) plain_ptr->~PlainObjectType();
 
     ref_ptr->~RefType();
   }
 
   AlignedStorage ref_storage;
   PyArrayObject *pyArray;
-  MatType *mat_ptr;
+  PlainObjectType *plain_ptr;
   RefType *ref_ptr;
 };
 #endif
@@ -110,18 +108,16 @@ namespace detail {
 #if EIGEN_VERSION_AT_LEAST(3, 2, 0)
 template <typename MatType, int Options, typename Stride>
 struct referent_storage<Eigen::Ref<MatType, Options, Stride> &> {
-  typedef ::eigenpy::details::referent_storage_eigen_ref<MatType, Options,
-                                                         Stride>
-      StorageType;
+  typedef Eigen::Ref<MatType, Options, Stride> RefType;
+  typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
   typedef typename ::eigenpy::aligned_storage<
       referent_size<StorageType &>::value>::type type;
 };
 
 template <typename MatType, int Options, typename Stride>
 struct referent_storage<const Eigen::Ref<const MatType, Options, Stride> &> {
-  typedef ::eigenpy::details::referent_storage_eigen_ref<const MatType, Options,
-                                                         Stride>
-      StorageType;
+  typedef Eigen::Ref<const MatType, Options, Stride> RefType;
+  typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
   typedef typename ::eigenpy::aligned_storage<
       referent_size<StorageType &>::value>::type type;
 };
@@ -173,7 +169,7 @@ struct rvalue_from_python_data<Eigen::PlainObjectBase<Derived> const &>
 template <typename MatType, int Options, typename Stride>
 struct rvalue_from_python_data<Eigen::Ref<MatType, Options, Stride> &>
     : rvalue_from_python_storage<Eigen::Ref<MatType, Options, Stride> &> {
-  typedef Eigen::Ref<MatType, Options, Stride> T;
+  typedef Eigen::Ref<MatType, Options, Stride> RefType;
 
 #if (!defined(__MWERKS__) || __MWERKS__ >= 0x3000) &&                        \
     (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 245) &&                 \
@@ -181,7 +177,7 @@ struct rvalue_from_python_data<Eigen::Ref<MatType, Options, Stride> &>
     !defined(BOOST_PYTHON_SYNOPSIS) /* Synopsis' OpenCXX has trouble parsing \
                                        this */
   // This must always be a POD struct with m_data its first member.
-  BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<T>,
+  BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<RefType>,
                                             stage1) == 0);
 #endif
 
@@ -199,9 +195,7 @@ struct rvalue_from_python_data<Eigen::Ref<MatType, Options, Stride> &>
 
   // Destroys any object constructed in the storage.
   ~rvalue_from_python_data() {
-    typedef ::eigenpy::details::referent_storage_eigen_ref<MatType, Options,
-                                                           Stride>
-        StorageType;
+    typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
     if (this->stage1.convertible == this->storage.bytes)
       static_cast<StorageType *>((void *)this->storage.bytes)->~StorageType();
   }
@@ -212,7 +206,7 @@ struct rvalue_from_python_data<
     const Eigen::Ref<const MatType, Options, Stride> &>
     : rvalue_from_python_storage<
           const Eigen::Ref<const MatType, Options, Stride> &> {
-  typedef const Eigen::Ref<const MatType, Options, Stride> T;
+  typedef Eigen::Ref<const MatType, Options, Stride> RefType;
 
 #if (!defined(__MWERKS__) || __MWERKS__ >= 0x3000) &&                        \
     (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 245) &&                 \
@@ -220,7 +214,7 @@ struct rvalue_from_python_data<
     !defined(BOOST_PYTHON_SYNOPSIS) /* Synopsis' OpenCXX has trouble parsing \
                                        this */
   // This must always be a POD struct with m_data its first member.
-  BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<T>,
+  BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<RefType>,
                                             stage1) == 0);
 #endif
 
@@ -238,9 +232,7 @@ struct rvalue_from_python_data<
 
   // Destroys any object constructed in the storage.
   ~rvalue_from_python_data() {
-    typedef ::eigenpy::details::referent_storage_eigen_ref<const MatType,
-                                                           Options, Stride>
-        StorageType;
+    typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
     if (this->stage1.convertible == this->storage.bytes)
       static_cast<StorageType *>((void *)this->storage.bytes)->~StorageType();
   }
@@ -269,8 +261,23 @@ void eigen_from_py_construct(
   memory->convertible = storage->storage.bytes;
 }
 
-template <typename MatType, typename _Scalar>
-struct EigenFromPy {
+template <typename EigenType,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct eigen_from_py_impl {
+  typedef typename EigenType::Scalar Scalar;
+
+  /// \brief Determine if pyObj can be converted into a MatType object
+  static void *convertible(PyObject *pyObj);
+
+  /// \brief Allocate memory and copy pyObj in the new storage
+  static void construct(PyObject *pyObj,
+                        bp::converter::rvalue_from_python_stage1_data *memory);
+
+  static void registration();
+};
+
+template <typename MatType>
+struct eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType> > {
   typedef typename MatType::Scalar Scalar;
 
   /// \brief Determine if pyObj can be converted into a MatType object
@@ -283,8 +290,20 @@ struct EigenFromPy {
   static void registration();
 };
 
-template <typename MatType, typename _Scalar>
-void *EigenFromPy<MatType, _Scalar>::convertible(PyObject *pyObj) {
+#ifdef EIGENPY_MSVC_COMPILER
+template <typename EigenType>
+struct EigenFromPy<EigenType,
+                   typename boost::remove_reference<EigenType>::type::Scalar>
+#else
+template <typename EigenType, typename _Scalar>
+struct EigenFromPy
+#endif
+    : eigen_from_py_impl<EigenType> {
+};
+
+template <typename MatType>
+void *eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType> >::convertible(
+    PyObject *pyObj) {
   if (!call_PyArray_Check(reinterpret_cast<PyObject *>(pyObj))) return 0;
 
   PyArrayObject *pyArray = reinterpret_cast<PyArrayObject *>(pyObj);
@@ -384,26 +403,33 @@ void *EigenFromPy<MatType, _Scalar>::convertible(PyObject *pyObj) {
   return pyArray;
 }
 
-template <typename MatType, typename _Scalar>
-void EigenFromPy<MatType, _Scalar>::construct(
+template <typename MatType>
+void eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType> >::construct(
     PyObject *pyObj, bp::converter::rvalue_from_python_stage1_data *memory) {
   eigen_from_py_construct<MatType>(pyObj, memory);
 }
 
-template <typename MatType, typename _Scalar>
-void EigenFromPy<MatType, _Scalar>::registration() {
+template <typename MatType>
+void eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType> >::registration() {
   bp::converter::registry::push_back(
-      reinterpret_cast<void *(*)(_object *)>(&EigenFromPy::convertible),
-      &EigenFromPy::construct, bp::type_id<MatType>()
+      reinterpret_cast<void *(*)(_object *)>(&eigen_from_py_impl::convertible),
+      &eigen_from_py_impl::construct, bp::type_id<MatType>()
 #ifndef BOOST_PYTHON_NO_PY_SIGNATURES
-                                   ,
+                                          ,
       &eigenpy::expected_pytype_for_arg<MatType>::get_pytype
 #endif
   );
 }
 
+template <typename EigenType,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct eigen_from_py_converter_impl;
+
+template <typename EigenType>
+struct EigenFromPyConverter : eigen_from_py_converter_impl<EigenType> {};
+
 template <typename MatType>
-struct EigenFromPyConverter {
+struct eigen_from_py_converter_impl<MatType, Eigen::MatrixBase<MatType> > {
   static void registration() {
     EigenFromPy<MatType>::registration();
 
@@ -535,4 +561,8 @@ struct EigenFromPy<const Eigen::Ref<const MatType, Options, Stride> > {
 
 }  // namespace eigenpy
 
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+#include "eigenpy/tensor/eigen-from-python.hpp"
+#endif
+
 #endif  // __eigenpy_eigen_from_python_hpp__
diff --git a/include/eigenpy/eigen-to-python.hpp b/include/eigenpy/eigen-to-python.hpp
index 96f2afdec..d7084ddf2 100644
--- a/include/eigenpy/eigen-to-python.hpp
+++ b/include/eigenpy/eigen-to-python.hpp
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2014-2020 CNRS INRIA
+// Copyright (c) 2014-2023 CNRS INRIA
 //
 
 #ifndef __eigenpy_eigen_to_python_hpp__
@@ -12,6 +12,7 @@
 #include "eigenpy/eigen-allocator.hpp"
 #include "eigenpy/numpy-allocator.hpp"
 #include "eigenpy/numpy-type.hpp"
+#include "eigenpy/registration.hpp"
 
 namespace boost {
 namespace python {
@@ -60,8 +61,33 @@ struct to_python_indirect<
 
 namespace eigenpy {
 
-template <typename MatType, typename _Scalar>
-struct EigenToPy {
+EIGENPY_DOCUMENTATION_START_IGNORE
+
+template <typename EigenType,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct eigen_to_py_impl;
+
+template <typename MatType>
+struct eigen_to_py_impl_matrix;
+
+template <typename MatType>
+struct eigen_to_py_impl<MatType, Eigen::MatrixBase<MatType> >
+    : eigen_to_py_impl_matrix<MatType> {};
+
+template <typename MatType>
+struct eigen_to_py_impl<MatType&, Eigen::MatrixBase<MatType> >
+    : eigen_to_py_impl_matrix<MatType&> {};
+
+template <typename MatType>
+struct eigen_to_py_impl<const MatType, const Eigen::MatrixBase<MatType> >
+    : eigen_to_py_impl_matrix<const MatType> {};
+
+template <typename MatType>
+struct eigen_to_py_impl<const MatType&, const Eigen::MatrixBase<MatType> >
+    : eigen_to_py_impl_matrix<const MatType&> {};
+
+template <typename MatType>
+struct eigen_to_py_impl_matrix {
   static PyObject* convert(
       typename boost::add_reference<
           typename boost::add_const<MatType>::type>::type mat) {
@@ -91,39 +117,53 @@ struct EigenToPy {
     // Create an instance (either np.array or np.matrix)
     return NumpyType::make(pyArray).ptr();
   }
-
-  static PyTypeObject const* get_pytype() { return getPyArrayType(); }
 };
 
-template <typename MatType, int Options, typename Stride, typename _Scalar>
-struct EigenToPy<Eigen::Ref<MatType, Options, Stride>, _Scalar> {
-  static PyObject* convert(const Eigen::Ref<MatType, Options, Stride>& mat) {
-    typedef Eigen::Ref<MatType, Options, Stride> EigenRef;
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename TensorType>
+struct eigen_to_py_impl_tensor;
 
-    assert((mat.rows() < INT_MAX) && (mat.cols() < INT_MAX) &&
-           "Matrix range larger than int ... should never happen.");
-    const npy_intp R = (npy_intp)mat.rows(), C = (npy_intp)mat.cols();
+template <typename TensorType>
+struct eigen_to_py_impl<TensorType, Eigen::TensorBase<TensorType> >
+    : eigen_to_py_impl_tensor<TensorType> {};
 
-    PyArrayObject* pyArray;
-    // Allocate Python memory
-    if ((((!(C == 1) != !(R == 1)) && !MatType::IsVectorAtCompileTime) ||
-         MatType::IsVectorAtCompileTime) &&
-        NumpyType::getType() ==
-            ARRAY_TYPE)  // Handle array with a single dimension
-    {
-      npy_intp shape[1] = {C == 1 ? R : C};
-      pyArray = NumpyAllocator<EigenRef>::allocate(const_cast<EigenRef&>(mat),
-                                                   1, shape);
-    } else {
-      npy_intp shape[2] = {R, C};
-      pyArray = NumpyAllocator<EigenRef>::allocate(const_cast<EigenRef&>(mat),
-                                                   2, shape);
-    }
+template <typename TensorType>
+struct eigen_to_py_impl<const TensorType, const Eigen::TensorBase<TensorType> >
+    : eigen_to_py_impl_tensor<const TensorType> {};
+
+template <typename TensorType>
+struct eigen_to_py_impl_tensor {
+  static PyObject* convert(
+      typename boost::add_reference<
+          typename boost::add_const<TensorType>::type>::type tensor) {
+    //    typedef typename boost::remove_const<
+    //        typename boost::remove_reference<Tensor>::type>::type
+    //        TensorDerived;
+
+    static const int NumIndices = TensorType::NumIndices;
+    npy_intp shape[NumIndices];
+    for (int k = 0; k < NumIndices; ++k) shape[k] = tensor.dimension(k);
+
+    PyArrayObject* pyArray = NumpyAllocator<TensorType>::allocate(
+        const_cast<TensorType&>(tensor), NumIndices, shape);
 
     // Create an instance (either np.array or np.matrix)
     return NumpyType::make(pyArray).ptr();
   }
+};
+#endif
 
+EIGENPY_DOCUMENTATION_END_IGNORE
+
+#ifdef EIGENPY_MSVC_COMPILER
+template <typename EigenType>
+struct EigenToPy<EigenType,
+                 typename boost::remove_reference<EigenType>::type::Scalar>
+#else
+template <typename EigenType, typename _Scalar>
+struct EigenToPy
+#endif
+    : eigen_to_py_impl<EigenType> {
   static PyTypeObject const* get_pytype() { return getPyArrayType(); }
 };
 
diff --git a/include/eigenpy/eigen-typedef.hpp b/include/eigenpy/eigen-typedef.hpp
index b6310e36e..d195d11a6 100644
--- a/include/eigenpy/eigen-typedef.hpp
+++ b/include/eigenpy/eigen-typedef.hpp
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2020 INRIA
+// Copyright (c) 2020-2023 INRIA
 //
 
 #ifndef __eigenpy_eigen_typedef_hpp__
@@ -31,6 +31,7 @@
   EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, Eigen::Dynamic, X) \
   EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 2)           \
   EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 3)           \
-  EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 4)
+  EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 4)           \
+  EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, 1, 1)
 
 #endif  // ifndef __eigenpy_eigen_typedef_hpp__
diff --git a/include/eigenpy/eigenpy.hpp b/include/eigenpy/eigenpy.hpp
index f9e5de952..bdbca6f4c 100644
--- a/include/eigenpy/eigenpy.hpp
+++ b/include/eigenpy/eigenpy.hpp
@@ -1,13 +1,12 @@
 /*
  * Copyright 2014-2019, CNRS
- * Copyright 2018-2020, INRIA
+ * Copyright 2018-2023, INRIA
  */
 
 #ifndef __eigenpy_eigenpy_hpp__
 #define __eigenpy_eigenpy_hpp__
 
 #include "eigenpy/fwd.hpp"
-#include "eigenpy/deprecated.hpp"
 #include "eigenpy/eigen-typedef.hpp"
 #include "eigenpy/expose.hpp"
 
@@ -20,22 +19,18 @@ namespace eigenpy {
  */
 void EIGENPY_DLLAPI enableEigenPy();
 
-/* Enable the Eigen--Numpy serialization for the templated MatrixBase class.
- * The second template argument is used for inheritance of Eigen classes. If
- * using a native Eigen::MatrixBase, simply repeat the same arg twice. */
+/* Enable the Eigen--Numpy serialization for the templated MatType class.*/
 template <typename MatType>
 void enableEigenPySpecific();
 
-/* Enable the Eigen--Numpy serialization for the templated MatrixBase class.
- * The second template argument is used for inheritance of Eigen classes. If
- * using a native Eigen::MatrixBase, simply repeat the same arg twice. */
-template <typename MatType, typename EigenEquivalentType>
-EIGENPY_DEPRECATED void enableEigenPySpecific();
-
 template <typename Scalar, int Options>
 EIGEN_DONT_INLINE void exposeType() {
   EIGENPY_MAKE_TYPEDEFS_ALL_SIZES(Scalar, Options, s);
 
+  EIGENPY_UNUSED_TYPE(Vector1s);
+  EIGENPY_UNUSED_TYPE(RowVector1s);
+  ENABLE_SPECIFIC_MATRIX_TYPE(Matrix1s);
+
   ENABLE_SPECIFIC_MATRIX_TYPE(Vector2s);
   ENABLE_SPECIFIC_MATRIX_TYPE(RowVector2s);
   ENABLE_SPECIFIC_MATRIX_TYPE(Matrix2s);
@@ -62,6 +57,12 @@ EIGEN_DONT_INLINE void exposeType() {
 template <typename Scalar>
 EIGEN_DONT_INLINE void exposeType() {
   exposeType<Scalar, 0>();
+
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+  enableEigenPySpecific<Eigen::Tensor<Scalar, 1> >();
+  enableEigenPySpecific<Eigen::Tensor<Scalar, 2> >();
+  enableEigenPySpecific<Eigen::Tensor<Scalar, 3> >();
+#endif
 }
 
 }  // namespace eigenpy
diff --git a/include/eigenpy/fwd.hpp b/include/eigenpy/fwd.hpp
index 3c30d6639..c44d10333 100644
--- a/include/eigenpy/fwd.hpp
+++ b/include/eigenpy/fwd.hpp
@@ -61,6 +61,9 @@
   EIGENPY_PRAGMA_WARNING(                    \
       EIGENPY_STRINGCAT("this file is deprecated: ", the_message))
 
+#define EIGENPY_DOCUMENTATION_START_IGNORE  /// \cond
+#define EIGENPY_DOCUMENTATION_END_IGNORE    /// \endcond
+
 #include "eigenpy/config.hpp"
 
 // Silence a warning about a deprecated use of boost bind by boost python
@@ -85,6 +88,11 @@ namespace bp = boost::python;
 #include <Eigen/Core>
 #include <Eigen/Geometry>
 
+#ifdef EIGENPY_WITH_CXX11_SUPPORT
+#include <unsupported/Eigen/CXX11/Tensor>
+#define EIGENPY_WITH_TENSOR_SUPPORT
+#endif
+
 #if EIGEN_VERSION_AT_LEAST(3, 2, 90)
 #define EIGENPY_DEFAULT_ALIGNMENT_VALUE Eigen::Aligned16
 #else
@@ -96,6 +104,7 @@ namespace bp = boost::python;
 #define EIGENPY_NO_ALIGNMENT_VALUE Eigen::Unaligned
 
 #define EIGENPY_UNUSED_VARIABLE(var) (void)(var)
+#define EIGENPY_UNUSED_TYPE(type) EIGENPY_UNUSED_VARIABLE((type *)(NULL))
 
 #ifdef EIGENPY_WITH_CXX11_SUPPORT
 #include <memory>
@@ -114,6 +123,50 @@ template <typename MatType,
           typename Scalar =
               typename boost::remove_reference<MatType>::type::Scalar>
 struct EigenFromPy;
+
+template <typename T>
+struct remove_const_reference {
+  typedef typename boost::remove_const<
+      typename boost::remove_reference<T>::type>::type type;
+};
+
+template <typename EigenType>
+struct get_eigen_base_type {
+  typedef typename remove_const_reference<EigenType>::type EigenType_;
+  typedef typename boost::mpl::if_<
+      boost::is_base_of<Eigen::MatrixBase<EigenType_>, EigenType_>,
+      Eigen::MatrixBase<EigenType_>
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+      ,
+      typename boost::mpl::if_<
+          boost::is_base_of<Eigen::TensorBase<EigenType_>, EigenType_>,
+          Eigen::TensorBase<EigenType_>, void>::type
+#else
+      ,
+      void
+#endif
+      >::type _type;
+
+  typedef typename boost::mpl::if_<
+      boost::is_const<typename boost::remove_reference<EigenType>::type>,
+      const _type, _type>::type type;
+};
+
+template <typename EigenType>
+struct get_eigen_ref_plain_type;
+
+template <typename MatType, int Options, typename Stride>
+struct get_eigen_ref_plain_type<Eigen::Ref<MatType, Options, Stride> > {
+  typedef typename Eigen::internal::traits<
+      Eigen::Ref<MatType, Options, Stride> >::PlainObjectType type;
+};
+
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename TensorType>
+struct get_eigen_ref_plain_type<Eigen::TensorRef<TensorType> > {
+  typedef TensorType type;
+};
+#endif
 }  // namespace eigenpy
 
 #include "eigenpy/alignment.hpp"
diff --git a/include/eigenpy/numpy-allocator.hpp b/include/eigenpy/numpy-allocator.hpp
index 30c287366..61653940a 100644
--- a/include/eigenpy/numpy-allocator.hpp
+++ b/include/eigenpy/numpy-allocator.hpp
@@ -1,19 +1,49 @@
 /*
- * Copyright 2020-2022 INRIA
+ * Copyright 2020-2023 INRIA
  */
 
 #ifndef __eigenpy_numpy_allocator_hpp__
 #define __eigenpy_numpy_allocator_hpp__
 
-#include "eigenpy/eigen-allocator.hpp"
 #include "eigenpy/fwd.hpp"
+#include "eigenpy/eigen-allocator.hpp"
 #include "eigenpy/numpy-type.hpp"
 #include "eigenpy/register.hpp"
 
 namespace eigenpy {
 
+template <typename EigenType, typename BaseType>
+struct numpy_allocator_impl;
+
+template <typename EigenType>
+struct numpy_allocator_impl_matrix;
+
+template <typename MatType>
+struct numpy_allocator_impl<
+    MatType, Eigen::MatrixBase<typename remove_const_reference<MatType>::type> >
+    : numpy_allocator_impl_matrix<MatType> {};
+
+template <typename MatType>
+struct numpy_allocator_impl<
+    const MatType,
+    const Eigen::MatrixBase<typename remove_const_reference<MatType>::type> >
+    : numpy_allocator_impl_matrix<const MatType> {};
+
+// template <typename MatType>
+// struct numpy_allocator_impl<MatType &, Eigen::MatrixBase<MatType> > :
+// numpy_allocator_impl_matrix<MatType &>
+//{};
+
+template <typename MatType>
+struct numpy_allocator_impl<const MatType &, const Eigen::MatrixBase<MatType> >
+    : numpy_allocator_impl_matrix<const MatType &> {};
+
+template <typename EigenType,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct NumpyAllocator : numpy_allocator_impl<EigenType, BaseType> {};
+
 template <typename MatType>
-struct NumpyAllocator {
+struct numpy_allocator_impl_matrix {
   template <typename SimilarMatrixType>
   static PyArrayObject *allocate(
       const Eigen::MatrixBase<SimilarMatrixType> &mat, npy_intp nd,
@@ -31,8 +61,40 @@ struct NumpyAllocator {
   }
 };
 
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+
+template <typename TensorType>
+struct numpy_allocator_impl_tensor;
+
+template <typename TensorType>
+struct numpy_allocator_impl<TensorType, Eigen::TensorBase<TensorType> >
+    : numpy_allocator_impl_tensor<TensorType> {};
+
+template <typename TensorType>
+struct numpy_allocator_impl<const TensorType,
+                            const Eigen::TensorBase<TensorType> >
+    : numpy_allocator_impl_tensor<const TensorType> {};
+
+template <typename TensorType>
+struct numpy_allocator_impl_tensor {
+  template <typename TensorDerived>
+  static PyArrayObject *allocate(const TensorDerived &tensor, npy_intp nd,
+                                 npy_intp *shape) {
+    const int code = Register::getTypeCode<typename TensorDerived::Scalar>();
+    PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_SimpleNew(
+        static_cast<int>(nd), shape, code);
+
+    // Copy data
+    EigenAllocator<TensorDerived>::copy(
+        static_cast<const TensorDerived &>(tensor), pyArray);
+
+    return pyArray;
+  }
+};
+#endif
+
 template <typename MatType>
-struct NumpyAllocator<MatType &> {
+struct numpy_allocator_impl_matrix<MatType &> {
   template <typename SimilarMatrixType>
   static PyArrayObject *allocate(Eigen::PlainObjectBase<SimilarMatrixType> &mat,
                                  npy_intp nd, npy_intp *shape) {
@@ -58,7 +120,7 @@ struct NumpyAllocator<MatType &> {
 #if EIGEN_VERSION_AT_LEAST(3, 2, 0)
 
 template <typename MatType, int Options, typename Stride>
-struct NumpyAllocator<Eigen::Ref<MatType, Options, Stride> > {
+struct numpy_allocator_impl_matrix<Eigen::Ref<MatType, Options, Stride> > {
   typedef Eigen::Ref<MatType, Options, Stride> RefType;
 
   static PyArrayObject *allocate(RefType &mat, npy_intp nd, npy_intp *shape) {
@@ -93,7 +155,7 @@ struct NumpyAllocator<Eigen::Ref<MatType, Options, Stride> > {
 #endif
 
 template <typename MatType>
-struct NumpyAllocator<const MatType &> {
+struct numpy_allocator_impl_matrix<const MatType &> {
   template <typename SimilarMatrixType>
   static PyArrayObject *allocate(
       const Eigen::PlainObjectBase<SimilarMatrixType> &mat, npy_intp nd,
@@ -122,7 +184,8 @@ struct NumpyAllocator<const MatType &> {
 #if EIGEN_VERSION_AT_LEAST(3, 2, 0)
 
 template <typename MatType, int Options, typename Stride>
-struct NumpyAllocator<const Eigen::Ref<const MatType, Options, Stride> > {
+struct numpy_allocator_impl_matrix<
+    const Eigen::Ref<const MatType, Options, Stride> > {
   typedef const Eigen::Ref<const MatType, Options, Stride> RefType;
 
   static PyArrayObject *allocate(RefType &mat, npy_intp nd, npy_intp *shape) {
@@ -156,6 +219,70 @@ struct NumpyAllocator<const Eigen::Ref<const MatType, Options, Stride> > {
   }
 };
 
+#endif
+
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename TensorType>
+struct numpy_allocator_impl_tensor<Eigen::TensorRef<TensorType> > {
+  typedef Eigen::TensorRef<TensorType> RefType;
+
+  static PyArrayObject *allocate(RefType &tensor, npy_intp nd,
+                                 npy_intp *shape) {
+    typedef typename RefType::Scalar Scalar;
+    static const bool IsRowMajor = TensorType::Options & Eigen::RowMajorBit;
+    enum {
+      NPY_ARRAY_MEMORY_CONTIGUOUS =
+          IsRowMajor ? NPY_ARRAY_CARRAY : NPY_ARRAY_FARRAY
+    };
+
+    if (NumpyType::sharedMemory()) {
+      const int Scalar_type_code = Register::getTypeCode<Scalar>();
+      //      static const Index NumIndices = TensorType::NumIndices;
+
+      //      const int elsize =
+      //      call_PyArray_DescrFromType(Scalar_type_code)->elsize; npy_intp
+      //      strides[NumIndices];
+
+      PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_New(
+          getPyArrayType(), static_cast<int>(nd), shape, Scalar_type_code, NULL,
+          const_cast<Scalar *>(tensor.data()),
+          NPY_ARRAY_MEMORY_CONTIGUOUS | NPY_ARRAY_ALIGNED);
+
+      return pyArray;
+    } else {
+      return NumpyAllocator<TensorType>::allocate(tensor, nd, shape);
+    }
+  }
+};
+
+template <typename TensorType>
+struct numpy_allocator_impl_tensor<const Eigen::TensorRef<const TensorType> > {
+  typedef const Eigen::TensorRef<const TensorType> RefType;
+
+  static PyArrayObject *allocate(RefType &tensor, npy_intp nd,
+                                 npy_intp *shape) {
+    typedef typename RefType::Scalar Scalar;
+    static const bool IsRowMajor = TensorType::Options & Eigen::RowMajorBit;
+    enum {
+      NPY_ARRAY_MEMORY_CONTIGUOUS_RO =
+          IsRowMajor ? NPY_ARRAY_CARRAY_RO : NPY_ARRAY_FARRAY_RO
+    };
+
+    if (NumpyType::sharedMemory()) {
+      const int Scalar_type_code = Register::getTypeCode<Scalar>();
+
+      PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_New(
+          getPyArrayType(), static_cast<int>(nd), shape, Scalar_type_code, NULL,
+          const_cast<Scalar *>(tensor.data()),
+          NPY_ARRAY_MEMORY_CONTIGUOUS_RO | NPY_ARRAY_ALIGNED);
+
+      return pyArray;
+    } else {
+      return NumpyAllocator<TensorType>::allocate(tensor, nd, shape);
+    }
+  }
+};
+
 #endif
 }  // namespace eigenpy
 
diff --git a/include/eigenpy/numpy-map.hpp b/include/eigenpy/numpy-map.hpp
index ae6b1e693..e73f1f42d 100644
--- a/include/eigenpy/numpy-map.hpp
+++ b/include/eigenpy/numpy-map.hpp
@@ -1,6 +1,6 @@
 /*
  * Copyright 2014-2019, CNRS
- * Copyright 2018-2020, INRIA
+ * Copyright 2018-2023, INRIA
  */
 
 #ifndef __eigenpy_numpy_map_hpp__
@@ -11,42 +11,40 @@
 #include "eigenpy/stride.hpp"
 
 namespace eigenpy {
+
 template <typename MatType, typename InputScalar, int AlignmentValue,
           typename Stride, bool IsVector = MatType::IsVectorAtCompileTime>
-struct NumpyMapTraits {};
+struct numpy_map_impl_matrix;
 
-/* Wrap a numpy::array with an Eigen::Map. No memory copy. */
-template <typename MatType, typename InputScalar,
-          int AlignmentValue = EIGENPY_NO_ALIGNMENT_VALUE,
-          typename Stride = typename StrideType<MatType>::type>
-struct NumpyMap {
-  typedef NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride> Impl;
-  typedef typename Impl::EigenMap EigenMap;
+template <typename EigenType, typename InputScalar, int AlignmentValue,
+          typename Stride,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct numpy_map_impl;
 
-  static EigenMap map(PyArrayObject* pyArray, bool swap_dimensions = false);
-};
-
-}  // namespace eigenpy
+template <typename MatType, typename InputScalar, int AlignmentValue,
+          typename Stride>
+struct numpy_map_impl<MatType, InputScalar, AlignmentValue, Stride,
+                      Eigen::MatrixBase<MatType> >
+    : numpy_map_impl_matrix<MatType, InputScalar, AlignmentValue, Stride> {};
 
-/* --- DETAILS
- * ------------------------------------------------------------------ */
-/* --- DETAILS
- * ------------------------------------------------------------------ */
-/* --- DETAILS
- * ------------------------------------------------------------------ */
+template <typename MatType, typename InputScalar, int AlignmentValue,
+          typename Stride>
+struct numpy_map_impl<const MatType, InputScalar, AlignmentValue, Stride,
+                      const Eigen::MatrixBase<MatType> >
+    : numpy_map_impl_matrix<const MatType, InputScalar, AlignmentValue,
+                            Stride> {};
 
-namespace eigenpy {
 template <typename MatType, typename InputScalar, int AlignmentValue,
           typename Stride>
-struct NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride, false> {
+struct numpy_map_impl_matrix<MatType, InputScalar, AlignmentValue, Stride,
+                             false> {
   typedef Eigen::Matrix<InputScalar, MatType::RowsAtCompileTime,
                         MatType::ColsAtCompileTime, MatType::Options>
       EquivalentInputMatrixType;
   typedef Eigen::Map<EquivalentInputMatrixType, AlignmentValue, Stride>
       EigenMap;
 
-  static EigenMap mapImpl(PyArrayObject* pyArray,
-                          bool swap_dimensions = false) {
+  static EigenMap map(PyArrayObject* pyArray, bool swap_dimensions = false) {
     enum {
       OuterStrideAtCompileTime = Stride::OuterStrideAtCompileTime,
       InnerStrideAtCompileTime = Stride::InnerStrideAtCompileTime,
@@ -135,15 +133,15 @@ struct NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride, false> {
 
 template <typename MatType, typename InputScalar, int AlignmentValue,
           typename Stride>
-struct NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride, true> {
+struct numpy_map_impl_matrix<MatType, InputScalar, AlignmentValue, Stride,
+                             true> {
   typedef Eigen::Matrix<InputScalar, MatType::RowsAtCompileTime,
                         MatType::ColsAtCompileTime, MatType::Options>
       EquivalentInputMatrixType;
   typedef Eigen::Map<EquivalentInputMatrixType, AlignmentValue, Stride>
       EigenMap;
 
-  static EigenMap mapImpl(PyArrayObject* pyArray,
-                          bool swap_dimensions = false) {
+  static EigenMap map(PyArrayObject* pyArray, bool swap_dimensions = false) {
     EIGENPY_UNUSED_VARIABLE(swap_dimensions);
     assert(PyArray_NDIM(pyArray) <= 2);
 
@@ -176,13 +174,58 @@ struct NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride, true> {
   }
 };
 
-template <typename MatType, typename InputScalar, int AlignmentValue,
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+
+template <typename TensorType, typename InputScalar, int AlignmentValue,
+          typename Stride>
+struct numpy_map_impl_tensor;
+
+template <typename TensorType, typename InputScalar, int AlignmentValue,
+          typename Stride>
+struct numpy_map_impl<TensorType, InputScalar, AlignmentValue, Stride,
+                      Eigen::TensorBase<TensorType> >
+    : numpy_map_impl_tensor<TensorType, InputScalar, AlignmentValue, Stride> {};
+
+template <typename TensorType, typename InputScalar, int AlignmentValue,
           typename Stride>
-typename NumpyMap<MatType, InputScalar, AlignmentValue, Stride>::EigenMap
-NumpyMap<MatType, InputScalar, AlignmentValue, Stride>::map(
-    PyArrayObject* pyArray, bool swap_dimensions) {
-  return Impl::mapImpl(pyArray, swap_dimensions);
-}
+struct numpy_map_impl<const TensorType, InputScalar, AlignmentValue, Stride,
+                      const Eigen::TensorBase<TensorType> >
+    : numpy_map_impl_tensor<const TensorType, InputScalar, AlignmentValue,
+                            Stride> {};
+
+template <typename TensorType, typename InputScalar, int AlignmentValue,
+          typename Stride>
+struct numpy_map_impl_tensor {
+  typedef TensorType Tensor;
+  typedef typename Eigen::internal::traits<TensorType>::Index Index;
+  static const int Options = Eigen::internal::traits<TensorType>::Options;
+  static const int NumIndices = TensorType::NumIndices;
+
+  typedef Eigen::Tensor<InputScalar, NumIndices, Options, Index>
+      EquivalentInputTensorType;
+  typedef typename EquivalentInputTensorType::Dimensions Dimensions;
+  typedef Eigen::TensorMap<EquivalentInputTensorType, Options> EigenMap;
+
+  static EigenMap map(PyArrayObject* pyArray, bool swap_dimensions = false) {
+    EIGENPY_UNUSED_VARIABLE(swap_dimensions);
+    assert(PyArray_NDIM(pyArray) == NumIndices || NumIndices == Eigen::Dynamic);
+
+    Eigen::DSizes<Index, NumIndices> dimensions;
+    for (int k = 0; k < PyArray_NDIM(pyArray); ++k)
+      dimensions[k] = PyArray_DIMS(pyArray)[k];
+
+    InputScalar* pyData = reinterpret_cast<InputScalar*>(PyArray_DATA(pyArray));
+    return EigenMap(pyData, dimensions);
+  }
+};
+#endif
+
+/* Wrap a numpy::array with an Eigen::Map. No memory copy. */
+template <typename EigenType, typename InputScalar,
+          int AlignmentValue = EIGENPY_NO_ALIGNMENT_VALUE,
+          typename Stride = typename StrideType<EigenType>::type>
+struct NumpyMap
+    : numpy_map_impl<EigenType, InputScalar, AlignmentValue, Stride> {};
 
 }  // namespace eigenpy
 
diff --git a/include/eigenpy/stride.hpp b/include/eigenpy/stride.hpp
index 10cd223e0..416809865 100644
--- a/include/eigenpy/stride.hpp
+++ b/include/eigenpy/stride.hpp
@@ -1,26 +1,65 @@
 /*
  * Copyright 2014-2019, CNRS
- * Copyright 2018-2019, INRIA
+ * Copyright 2018-2023, INRIA
  */
 
 #ifndef __eigenpy_stride_hpp__
 #define __eigenpy_stride_hpp__
 
-#include <Eigen/Core>
+#include <eigenpy/fwd.hpp>
 
 namespace eigenpy {
-template <typename MatType, int InnerStride = Eigen::Dynamic,
-          int OuterStride = Eigen::Dynamic,
+
+template <typename MatType, int InnerStride, int OuterStride,
           bool IsVectorAtCompileTime = MatType::IsVectorAtCompileTime>
-struct StrideType {
+struct stride_type_matrix {
   typedef Eigen::Stride<OuterStride, InnerStride> type;
 };
 
 template <typename MatType, int InnerStride, int OuterStride>
-struct StrideType<MatType, InnerStride, OuterStride, true> {
+struct stride_type_matrix<MatType, InnerStride, OuterStride, true> {
   typedef Eigen::InnerStride<InnerStride> type;
 };
 
+template <typename EigenType, int InnerStride, int OuterStride,
+          typename BaseType = typename get_eigen_base_type<EigenType>::type>
+struct stride_type;
+
+template <typename MatrixType, int InnerStride, int OuterStride>
+struct stride_type<MatrixType, InnerStride, OuterStride,
+                   Eigen::MatrixBase<MatrixType> > {
+  typedef
+      typename stride_type_matrix<MatrixType, InnerStride, OuterStride>::type
+          type;
+};
+
+template <typename MatrixType, int InnerStride, int OuterStride>
+struct stride_type<const MatrixType, InnerStride, OuterStride,
+                   const Eigen::MatrixBase<MatrixType> > {
+  typedef typename stride_type_matrix<const MatrixType, InnerStride,
+                                      OuterStride>::type type;
+};
+
+#ifdef EIGENPY_WITH_TENSOR_SUPPORT
+template <typename TensorType, int InnerStride, int OuterStride>
+struct stride_type<TensorType, InnerStride, OuterStride,
+                   Eigen::TensorBase<TensorType> > {
+  typedef Eigen::Stride<OuterStride, InnerStride> type;
+};
+
+template <typename TensorType, int InnerStride, int OuterStride>
+struct stride_type<const TensorType, InnerStride, OuterStride,
+                   const Eigen::TensorBase<TensorType> > {
+  typedef Eigen::Stride<OuterStride, InnerStride> type;
+};
+#endif
+
+template <typename EigenType, int InnerStride = Eigen::Dynamic,
+          int OuterStride = Eigen::Dynamic>
+struct StrideType {
+  typedef typename stride_type<EigenType, InnerStride, OuterStride>::type type;
+};
+
 }  // namespace eigenpy
 
 #endif  // ifndef __eigenpy_stride_hpp__
diff --git a/include/eigenpy/tensor/eigen-from-python.hpp b/include/eigenpy/tensor/eigen-from-python.hpp
new file mode 100644
index 000000000..ed4fed0e9
--- /dev/null
+++ b/include/eigenpy/tensor/eigen-from-python.hpp
@@ -0,0 +1,226 @@
+//
+// Copyright (c) 2023 INRIA
+//
+
+#ifndef __eigenpy_tensor_eigen_from_python_hpp__
+#define __eigenpy_tensor_eigen_from_python_hpp__
+
+#include "eigenpy/fwd.hpp"
+#include "eigenpy/eigen-allocator.hpp"
+#include "eigenpy/numpy-type.hpp"
+#include "eigenpy/scalar-conversion.hpp"
+
+namespace eigenpy {
+
+template <typename TensorType>
+struct expected_pytype_for_arg<TensorType, Eigen::TensorBase<TensorType> > {
+  static PyTypeObject const *get_pytype() {
+    PyTypeObject const *py_type = eigenpy::getPyArrayType();
+    return py_type;
+  }
+};
+
+}  // namespace eigenpy
+
+namespace boost {
+namespace python {
+namespace converter {
+
+template <typename Scalar, int Rank, int Options, typename IndexType>
+struct expected_pytype_for_arg<Eigen::Tensor<Scalar, Rank, Options, IndexType> >
+    : eigenpy::expected_pytype_for_arg<
+          Eigen::Tensor<Scalar, Rank, Options, IndexType> > {};
+
+template <typename Scalar, int Rank, int Options, typename IndexType>
+struct rvalue_from_python_data<
+    Eigen::Tensor<Scalar, Rank, Options, IndexType> const &>
+    : ::eigenpy::rvalue_from_python_data<
+          Eigen::Tensor<Scalar, Rank, Options, IndexType> const &> {
+  typedef Eigen::Tensor<Scalar, Rank, Options, IndexType> T;
+  EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(T const &)
+};
+
+template <typename Derived>
+struct rvalue_from_python_data<Eigen::TensorBase<Derived> const &>
+    : ::eigenpy::rvalue_from_python_data<Derived const &> {
+  EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(Derived const &)
+};
+
+}  // namespace converter
+}  // namespace python
+}  // namespace boost
+
+namespace boost {
+namespace python {
+namespace detail {
+template <typename TensorType>
+struct referent_storage<Eigen::TensorRef<TensorType> &> {
+  typedef Eigen::TensorRef<TensorType> RefType;
+  typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
+  typedef typename ::eigenpy::aligned_storage<
+      referent_size<StorageType &>::value>::type type;
+};
+
+template <typename TensorType>
+struct referent_storage<const Eigen::TensorRef<const TensorType> &> {
+  typedef Eigen::TensorRef<const TensorType> RefType;
+  typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
+  typedef typename ::eigenpy::aligned_storage<
+      referent_size<StorageType &>::value>::type type;
+};
+}  // namespace detail
+}  // namespace python
+}  // namespace boost
+
+namespace eigenpy {
+
+template <typename TensorType>
+struct eigen_from_py_impl<TensorType, Eigen::TensorBase<TensorType> > {
+  typedef typename TensorType::Scalar Scalar;
+
+  /// \brief Determine if pyObj can be converted into a MatType object
+  static void *convertible(PyObject *pyObj);
+
+  /// \brief Allocate memory and copy pyObj in the new storage
+  static void construct(PyObject *pyObj,
+                        bp::converter::rvalue_from_python_stage1_data *memory);
+
+  static void registration();
+};
+
+template <typename TensorType>
+void *
+eigen_from_py_impl<TensorType, Eigen::TensorBase<TensorType> >::convertible(
+    PyObject *pyObj) {
+  if (!call_PyArray_Check(reinterpret_cast<PyObject *>(pyObj))) return 0;
+
+  typedef typename Eigen::internal::traits<TensorType>::Index Index;
+  static const Index NumIndices = TensorType::NumIndices;
+
+  PyArrayObject *pyArray = reinterpret_cast<PyArrayObject *>(pyObj);
+
+  if (!np_type_is_convertible_into_scalar<Scalar>(
+          EIGENPY_GET_PY_ARRAY_TYPE(pyArray)))
+    return 0;
+
+  if (!(PyArray_NDIM(pyArray) == NumIndices || NumIndices == Eigen::Dynamic))
+    return 0;
+
+#ifdef NPY_1_8_API_VERSION
+  if (!(PyArray_FLAGS(pyArray)))
+#else
+  if (!(PyArray_FLAGS(pyArray) & NPY_ALIGNED))
+#endif
+  {
+    return 0;
+  }
+
+  return pyArray;
+}
+
+template <typename TensorType>
+void eigen_from_py_impl<TensorType, Eigen::TensorBase<TensorType> >::construct(
+    PyObject *pyObj, bp::converter::rvalue_from_python_stage1_data *memory) {
+  eigen_from_py_construct<TensorType>(pyObj, memory);
+}
+
+template <typename TensorType>
+void eigen_from_py_impl<TensorType,
+                        Eigen::TensorBase<TensorType> >::registration() {
+  bp::converter::registry::push_back(
+      reinterpret_cast<void *(*)(_object *)>(&eigen_from_py_impl::convertible),
+      &eigen_from_py_impl::construct, bp::type_id<TensorType>()
+#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
+                                          ,
+      &eigenpy::expected_pytype_for_arg<TensorType>::get_pytype
+#endif
+  );
+}
+
+template <typename TensorType>
+struct eigen_from_py_converter_impl<TensorType,
+                                    Eigen::TensorBase<TensorType> > {
+  static void registration() {
+    EigenFromPy<TensorType>::registration();
+
+    // Add conversion to Eigen::TensorBase<TensorType>
+    typedef Eigen::TensorBase<TensorType> TensorBase;
+    EigenFromPy<TensorBase>::registration();
+
+    // Add conversion to Eigen::TensorRef<TensorType>
+    typedef Eigen::TensorRef<TensorType> RefType;
+    EigenFromPy<RefType>::registration();
+
+    // Add conversion to Eigen::TensorRef<const TensorType>
+    typedef const Eigen::TensorRef<const TensorType> ConstRefType;
+    EigenFromPy<ConstRefType>::registration();
+  }
+};
+
+template <typename TensorType>
+struct EigenFromPy<Eigen::TensorBase<TensorType> > : EigenFromPy<TensorType> {
+  typedef EigenFromPy<TensorType> EigenFromPyDerived;
+  typedef Eigen::TensorBase<TensorType> Base;
+
+  static void registration() {
+    bp::converter::registry::push_back(
+        reinterpret_cast<void *(*)(_object *)>(&EigenFromPy::convertible),
+        &EigenFromPy::construct, bp::type_id<Base>()
+#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
+                                     ,
+        &eigenpy::expected_pytype_for_arg<TensorType>::get_pytype
+#endif
+    );
+  }
+};
+
+template <typename TensorType>
+struct EigenFromPy<Eigen::TensorRef<TensorType> > {
+  typedef Eigen::TensorRef<TensorType> RefType;
+  typedef typename TensorType::Scalar Scalar;
+
+  /// \brief Determine if pyObj can be converted into a MatType object
+  static void *convertible(PyObject *pyObj) {
+    if (!call_PyArray_Check(pyObj)) return 0;
+    PyArrayObject *pyArray = reinterpret_cast<PyArrayObject *>(pyObj);
+    if (!PyArray_ISWRITEABLE(pyArray)) return 0;
+    return EigenFromPy<TensorType>::convertible(pyObj);
+  }
+
+  static void registration() {
+    bp::converter::registry::push_back(
+        reinterpret_cast<void *(*)(_object *)>(&EigenFromPy::convertible),
+        &eigen_from_py_construct<RefType>, bp::type_id<RefType>()
+#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
+                                               ,
+        &eigenpy::expected_pytype_for_arg<TensorType>::get_pytype
+#endif
+    );
+  }
+};
+
+template <typename TensorType>
+struct EigenFromPy<const Eigen::TensorRef<const TensorType> > {
+  typedef const Eigen::TensorRef<const TensorType> ConstRefType;
+  typedef typename TensorType::Scalar Scalar;
+
+  /// \brief Determine if pyObj can be converted into a MatType object
+  static void *convertible(PyObject *pyObj) {
+    return EigenFromPy<TensorType>::convertible(pyObj);
+  }
+
+  static void registration() {
+    bp::converter::registry::push_back(
+        reinterpret_cast<void *(*)(_object *)>(&EigenFromPy::convertible),
+        &eigen_from_py_construct<ConstRefType>, bp::type_id<ConstRefType>()
+#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
+                                                    ,
+        &eigenpy::expected_pytype_for_arg<TensorType>::get_pytype
+#endif
+    );
+  }
+};
+
+}  // namespace eigenpy
+
+#endif  // __eigenpy_tensor_eigen_from_python_hpp__


Test patch:
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index 8e0f194a8..667c41929 100644
--- a/unittest/CMakeLists.txt
+++ b/unittest/CMakeLists.txt
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2014-2019 CNRS Copyright (c) 2018-2021 INRIA
+# Copyright (c) 2014-2019 CNRS Copyright (c) 2018-2023 INRIA
 #
 
 macro(ADD_LIB_UNIT_TEST test)
@@ -25,6 +25,7 @@ macro(ADD_LIB_UNIT_TEST test)
 endmacro(ADD_LIB_UNIT_TEST)
 
 add_lib_unit_test(matrix)
+add_lib_unit_test(tensor)
 add_lib_unit_test(geometry)
 add_lib_unit_test(complex)
 add_lib_unit_test(return_by_ref)
@@ -39,6 +40,8 @@ endif()
 add_lib_unit_test(std_vector)
 
 add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest")
+
+add_python_unit_test("py-tensor" "unittest/python/test_tensor.py" "unittest")
 add_python_unit_test("py-geometry" "unittest/python/test_geometry.py"
                      "unittest")
 add_python_unit_test("py-complex" "unittest/python/test_complex.py" "unittest")
diff --git a/unittest/matrix.cpp b/unittest/matrix.cpp
index 95f97da55..e202c572b 100644
--- a/unittest/matrix.cpp
+++ b/unittest/matrix.cpp
@@ -19,6 +19,11 @@ Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> matrix1x1(
   return ReturnType::Constant(1, 1, value);
 }
 
+template <typename Scalar>
+void matrix1x1_input(const Eigen::Matrix<Scalar, 1, 1>& mat) {
+  std::cout << mat << std::endl;
+}
+
 Eigen::VectorXd emptyVector() {
   Eigen::VectorXd vec;
   vec.resize(0);
@@ -146,6 +151,8 @@ BOOST_PYTHON_MODULE(matrix) {
 
   bp::def("vector1x1", vector1x1<double>);
   bp::def("matrix1x1", matrix1x1<double>);
+  bp::def("matrix1x1", matrix1x1_input<double>);
+  bp::def("matrix1x1_int", matrix1x1_input<int>);
 
   bp::def("naturals", naturalsXX);
   bp::def("naturalsX", naturalsX);
diff --git a/unittest/python/test_matrix.py b/unittest/python/test_matrix.py
index 0edf18896..af384c837 100644
--- a/unittest/python/test_matrix.py
+++ b/unittest/python/test_matrix.py
@@ -15,6 +15,10 @@
 v = eigenpy.emptyVector()
 assert v.shape == (0,)
 
+if verbose:
+    print("===> From Py to Matrix1")
+eigenpy.matrix1x1(np.array([1]))
+
 if verbose:
     print("===> From MatrixXd to Py")
 M = eigenpy.naturals(3, 3, verbose)
diff --git a/unittest/python/test_tensor.py b/unittest/python/test_tensor.py
new file mode 100644
index 000000000..b82d34f91
--- /dev/null
+++ b/unittest/python/test_tensor.py
@@ -0,0 +1,32 @@
+from __future__ import print_function
+
+import numpy as np
+import tensor
+
+dim = np.array([10, 20, 30], dtype=np.int32)
+t = tensor.TensorContainer3(dim)
+r = t.get_ref()
+r[:] = 0.0
+c = t.get_copy()
+r2 = tensor.ref(r)
+cr = tensor.const_ref(r)
+c2 = tensor.copy(cr)
+
+assert np.all(c == r)
+assert np.all(r2 == r)
+assert np.all(cr == r)
+assert np.all(c2 == r)
+
+tensor.print_base(cr)
+tensor.print_ref(cr)
+tensor.print(cr)
+
+r2[:] = 100.0
+assert not np.all(c == r)
+assert not np.all(c2 == r)
+assert np.all(r2 == r)
+assert np.all(cr == r)
+
+tensor.print_base(cr)
+tensor.print_ref(cr)
+tensor.print(cr)
diff --git a/unittest/tensor.cpp b/unittest/tensor.cpp
new file mode 100644
index 000000000..80838ebfc
--- /dev/null
+++ b/unittest/tensor.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2023 INRIA
+ */
+
+#include <iostream>
+
+#include "eigenpy/eigenpy.hpp"
+namespace bp = boost::python;
+
+template <typename Scalar>
+Eigen::Matrix<Scalar, Eigen::Dynamic, 1> vector1x1(const Scalar& value) {
+  typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> ReturnType;
+  return ReturnType::Constant(1, value);
+}
+
+template <typename Scalar>
+Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> matrix1x1(
+    const Scalar& value) {
+  typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> ReturnType;
+  return ReturnType::Constant(1, 1, value);
+}
+
+template <typename Tensor>
+Eigen::TensorRef<Tensor> make_ref(Tensor& tensor) {
+  return Eigen::TensorRef<Tensor>(tensor);
+}
+
+template <typename Tensor>
+void fill(Eigen::TensorRef<Tensor> tensor, typename Tensor::Scalar value) {
+  for (Eigen::DenseIndex k = 0; k < tensor.size(); ++k)
+    tensor.coeffRef(k) = value;
+}
+
+template <typename Tensor>
+void print(const Tensor& tensor) {
+  std::cout << tensor << std::endl;
+}
+
+template <typename Tensor>
+void print_ref(const Eigen::TensorRef<const Tensor> tensor) {
+  print(tensor);
+}
+
+template <typename Tensor>
+void print_base(const Eigen::TensorBase<Tensor>& tensor) {
+  print(tensor);
+}
+
+template <typename Tensor>
+Tensor copy(const Eigen::TensorBase<Tensor>& tensor) {
+  return const_cast<Tensor&>(static_cast<const Tensor&>(tensor));
+}
+
+template <typename Tensor>
+Eigen::TensorRef<Tensor> ref(Eigen::TensorRef<Tensor> tensor) {
+  return tensor;
+}
+
+template <typename Tensor>
+const Eigen::TensorRef<const Tensor> const_ref(
+    const Eigen::TensorRef<const Tensor> tensor) {
+  return tensor;
+}
+
+template <typename Scalar, int Rank>
+Eigen::Tensor<Scalar, Rank> emptyTensor() {
+  return Eigen::Tensor<Scalar, Rank>();
+}
+
+template <typename Scalar>
+Eigen::Tensor<Scalar, 1> zeroTensor1(const Eigen::DenseIndex r) {
+  Eigen::Tensor<Scalar, 1> tensor(r);
+  tensor.setZero();
+  return tensor;
+}
+
+template <typename Scalar>
+Eigen::Tensor<Scalar, 2> zeroTensor2(const Eigen::DenseIndex r,
+                                     const Eigen::DenseIndex s) {
+  Eigen::Tensor<Scalar, 2> tensor(r, s);
+  tensor.setZero();
+  return tensor;
+}
+
+template <typename Scalar>
+Eigen::Tensor<Scalar, 3> zeroTensor3(const Eigen::DenseIndex r,
+                                     const Eigen::DenseIndex s,
+                                     const Eigen::DenseIndex t) {
+  Eigen::Tensor<Scalar, 3> tensor(r, s, t);
+  tensor.setZero();
+  return tensor;
+}
+
+template <typename Scalar>
+Eigen::Tensor<Scalar, 1> createTensor1(const Eigen::DenseIndex r,
+                                       Scalar value) {
+  Eigen::Tensor<Scalar, 1> tensor(r);
+  fill(make_ref(tensor), value);
+  return tensor;
+}
+
+template <typename Scalar>
+Eigen::Tensor<Scalar, 2> createTensor2(const Eigen::DenseIndex r,
+                                       const Eigen::DenseIndex s,
+                                       Scalar value) {
+  Eigen::Tensor<Scalar, 2> tensor(r, s);
+  fill(make_ref(tensor), value);
+  return tensor;
+}
+
+template <typename Scalar>
+Eigen::Tensor<Scalar, 3> createTensor3(const Eigen::DenseIndex r,
+                                       const Eigen::DenseIndex s,
+                                       const Eigen::DenseIndex t,
+                                       Scalar value) {
+  Eigen::Tensor<Scalar, 3> tensor(r, s, t);
+  fill(make_ref(tensor), value);
+  return tensor;
+}
+
+template <typename Scalar, int Rank>
+struct TensorContainer {
+  typedef Eigen::Tensor<Scalar, Rank> Tensor;
+  typedef Eigen::TensorRef<Tensor> TensorRef;
+  typedef Eigen::Matrix<Scalar, Rank, 1> Dimensions;
+
+  Tensor m_tensor;
+  TensorContainer(const Dimensions& dims) {
+    typedef Eigen::array<typename Tensor::Index, Rank> InternalDimension;
+    InternalDimension _dims;
+    for (size_t k = 0; k < Rank; ++k) _dims[k] = dims[Eigen::DenseIndex(k)];
+
+    m_tensor = Tensor(_dims);
+  }
+
+  Tensor get_copy() const { return m_tensor; }
+  TensorRef get_ref() { return TensorRef(m_tensor); }
+};
+
+template <typename Scalar, int Rank>
+void exposeTensorContainer() {
+  typedef TensorContainer<Scalar, Rank> T;
+  const std::string class_name = "TensorContainer" + std::to_string(Rank);
+  bp::class_<T>(class_name.c_str(), bp::no_init)
+      .def(bp::init<typename T::Dimensions>())
+      .def("get_copy", &T::get_copy)
+      .def("get_ref", &T::get_ref,
+           bp::with_custodian_and_ward_postcall<0, 1>());
+}
+
+BOOST_PYTHON_MODULE(tensor) {
+  using namespace Eigen;
+  eigenpy::enableEigenPy();
+
+  typedef Eigen::Tensor<double, 1> Tensor1;
+  typedef Eigen::Tensor<double, 2> Tensor2;
+  typedef Eigen::Tensor<double, 3> Tensor3;
+
+  bp::def("emptyTensor1", emptyTensor<double, 1>);
+  bp::def("emptyTensor2", emptyTensor<double, 2>);
+  bp::def("emptyTensor3", emptyTensor<double, 3>);
+
+  bp::def("zeroTensor1", zeroTensor1<double>);
+  bp::def("zeroTensor2", zeroTensor2<double>);
+  bp::def("zeroTensor3", zeroTensor3<double>);
+
+  bp::def("createTensor1", createTensor1<double>);
+  bp::def("createTensor2", createTensor2<double>);
+  bp::def("createTensor3", createTensor3<double>);
+
+  bp::def("print", print<Tensor1>);
+  bp::def("print", print<Tensor2>);
+  bp::def("print", print<Tensor3>);
+
+  bp::def("print_ref", print_ref<Tensor1>);
+  bp::def("print_ref", print_ref<Tensor2>);
+  bp::def("print_ref", print_ref<Tensor3>);
+
+  bp::def("print_base", print_base<Tensor1>);
+  bp::def("print_base", print_base<Tensor2>);
+  bp::def("print_base", print_base<Tensor3>);
+
+  bp::def("copy", copy<Tensor1>);
+  bp::def("copy", copy<Tensor2>);
+  bp::def("copy", copy<Tensor3>);
+
+  bp::def("fill", fill<Tensor1>);
+  bp::def("fill", fill<Tensor2>);
+  bp::def("fill", fill<Tensor3>);
+
+  bp::def("ref", ref<Tensor1>, bp::with_custodian_and_ward_postcall<0, 1>());
+  bp::def("ref", ref<Tensor2>, bp::with_custodian_and_ward_postcall<0, 1>());
+  bp::def("ref", ref<Tensor3>, bp::with_custodian_and_ward_postcall<0, 1>());
+
+  bp::def("const_ref", const_ref<Tensor1>,
+          bp::with_custodian_and_ward_postcall<0, 1>());
+  bp::def("const_ref", const_ref<Tensor2>,
+          bp::with_custodian_and_ward_postcall<0, 1>());
+  bp::def("const_ref", const_ref<Tensor3>,
+          bp::with_custodian_and_ward_postcall<0, 1>());
+
+  exposeTensorContainer<double, 1>();
+  exposeTensorContainer<double, 2>();
+  exposeTensorContainer<double, 3>();
+}