aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWenzel Jakob <wenzel.jakob@epfl.ch>2016-05-05 20:33:54 +0200
committerWenzel Jakob <wenzel.jakob@epfl.ch>2016-05-05 21:44:29 +0200
commit9e0a0568fed8c05ba7bb8d5101af7b6c407fb4eb (patch)
tree74164202b9353c27ef6cc9eba7914b7e95fcef72
parent9ac5bc55314457e69dcffd352e79c1332f454e25 (diff)
downloadplatform_external_python_pybind11-9e0a0568fed8c05ba7bb8d5101af7b6c407fb4eb.tar.gz
platform_external_python_pybind11-9e0a0568fed8c05ba7bb8d5101af7b6c407fb4eb.tar.bz2
platform_external_python_pybind11-9e0a0568fed8c05ba7bb8d5101af7b6c407fb4eb.zip
transparent conversion of dense and sparse Eigen types
-rw-r--r--CMakeLists.txt15
-rw-r--r--docs/advanced.rst309
-rw-r--r--docs/basics.rst4
-rw-r--r--docs/changelog.rst1
-rw-r--r--example/eigen.cpp73
-rw-r--r--example/eigen.py44
-rw-r--r--example/eigen.ref18
-rw-r--r--example/example.cpp8
-rw-r--r--include/pybind11/eigen.h261
-rw-r--r--include/pybind11/numpy.h29
-rw-r--r--include/pybind11/stl.h2
-rw-r--r--setup.py1
-rw-r--r--tools/FindEigen3.cmake81
13 files changed, 715 insertions, 131 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1fe3fdc..38297db 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -84,6 +84,11 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER_ID}"
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
endif()
+
+# Check if Eigen is available
+set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools")
+find_package(Eigen3 QUIET)
+
# Include path for pybind11 header files
include_directories(include)
@@ -96,6 +101,7 @@ set(PYBIND11_HEADERS
include/pybind11/common.h
include/pybind11/complex.h
include/pybind11/descr.h
+ include/pybind11/eigen.h
include/pybind11/functional.h
include/pybind11/numpy.h
include/pybind11/operators.h
@@ -125,6 +131,15 @@ set(PYBIND11_EXAMPLES
example/issues.cpp
)
+if (EIGEN3_FOUND)
+ include_directories(${EIGEN3_INCLUDE_DIR})
+ list(APPEND PYBIND11_EXAMPLES example/eigen.cpp)
+ add_definitions(-DPYBIND11_TEST_EIGEN)
+ message(STATUS "Building Eigen testcase")
+else()
+ message(STATUS "NOT Building Eigen testcase")
+endif()
+
# Create the binding library
add_library(example SHARED
${PYBIND11_HEADERS}
diff --git a/docs/advanced.rst b/docs/advanced.rst
index eb11176..901faa7 100644
--- a/docs/advanced.rst
+++ b/docs/advanced.rst
@@ -792,13 +792,157 @@ There is also a special exception :class:`cast_error` that is thrown by
:func:`handle::call` when the input arguments cannot be converted to Python
objects.
+.. _opaque:
+
+Treating STL data structures as opaque objects
+==============================================
+
+pybind11 heavily relies on a template matching mechanism to convert parameters
+and return values that are constructed from STL data types such as vectors,
+linked lists, hash tables, etc. This even works in a recursive manner, for
+instance to deal with lists of hash maps of pairs of elementary and custom
+types, etc.
+
+However, a fundamental limitation of this approach is that internal conversions
+between Python and C++ types involve a copy operation that prevents
+pass-by-reference semantics. What does this mean?
+
+Suppose we bind the following function
+
+.. code-block:: cpp
+
+ void append_1(std::vector<int> &v) {
+ v.push_back(1);
+ }
+
+and call it from Python, the following happens:
+
+.. code-block:: python
+
+ >>> v = [5, 6]
+ >>> append_1(v)
+ >>> print(v)
+ [5, 6]
+
+As you can see, when passing STL data structures by reference, modifications
+are not propagated back the Python side. A similar situation arises when
+exposing STL data structures using the ``def_readwrite`` or ``def_readonly``
+functions:
+
+.. code-block:: cpp
+
+ /* ... definition ... */
+
+ class MyClass {
+ std::vector<int> contents;
+ };
+
+ /* ... binding code ... */
+
+ py::class_<MyClass>(m, "MyClass")
+ .def(py::init<>)
+ .def_readwrite("contents", &MyClass::contents);
+
+In this case, properties can be read and written in their entirety. However, an
+``append`` operaton involving such a list type has no effect:
+
+.. code-block:: python
+
+ >>> m = MyClass()
+ >>> m.contents = [5, 6]
+ >>> print(m.contents)
+ [5, 6]
+ >>> m.contents.append(7)
+ >>> print(m.contents)
+ [5, 6]
+
+To deal with both of the above situations, pybind11 provides a macro named
+``PYBIND11_MAKE_OPAQUE(T)`` that disables the template-based conversion
+machinery of types, thus rendering them *opaque*. The contents of opaque
+objects are never inspected or extracted, hence they can be passed by
+reference. For instance, to turn ``std::vector<int>`` into an opaque type, add
+the declaration
+
+.. code-block:: cpp
+
+ PYBIND11_MAKE_OPAQUE(std::vector<int>);
+
+before any binding code (e.g. invocations to ``class_::def()``, etc.). This
+macro must be specified at the top level, since instantiates a partial template
+overload. If your binding code consists of multiple compilation units, it must
+be present in every file preceding any usage of ``std::vector<int>``. Opaque
+types must also have a corresponding ``class_`` declaration to associate them
+with a name in Python, and to define a set of available operations:
+
+.. code-block:: cpp
+
+ py::class_<std::vector<int>>(m, "IntVector")
+ .def(py::init<>())
+ .def("clear", &std::vector<int>::clear)
+ .def("pop_back", &std::vector<int>::pop_back)
+ .def("__len__", [](const std::vector<int> &v) { return v.size(); })
+ .def("__iter__", [](std::vector<int> &v) {
+ return py::make_iterator(v.begin(), v.end());
+ }, py::keep_alive<0, 1>()) /* Keep vector alive while iterator is used */
+ // ....
+
+
+.. seealso::
+
+ The file :file:`example/example14.cpp` contains a complete example that
+ demonstrates how to create and expose opaque types using pybind11 in more
+ detail.
+
+.. _eigen:
+
+Transparent conversion of dense and sparse Eigen data types
+===========================================================
+
+Eigen [#f1]_ is C++ header-based library for dense and sparse linear algebra. Due to
+its popularity and widespread adoption, pybind11 provides transparent
+conversion support between Eigen and Scientific Python linear algebra data types.
+
+Specifically, when including the optional header file :file:`pybind11/eigen.h`,
+pybind11 will automatically and transparently convert
+
+1. Static and dynamic Eigen dense vectors and matrices to instances of
+ ``numpy.ndarray`` (and vice versa).
+
+1. Eigen sparse vectors and matrices to instances of
+ ``scipy.sparse.csr_matrix``/``scipy.sparse.csc_matrix`` (and vice versa).
+
+This makes it possible to bind most kinds of functions that rely on these types.
+One major caveat are functions that take Eigen matrices *by reference* and modify
+them somehow, in which case the information won't be propagated to the caller.
+
+.. code-block:: cpp
+
+ /* The Python bindings of this function won't replicate
+ the intended effect of modifying the function argument */
+ void scale_by_2(Eigen::Vector3f &v) {
+ v *= 2;
+ }
+
+To see why this is, refer to the section on :ref:`opaque` (although that
+section specifically covers STL data types, the underlying issue is the same).
+The next two sections discuss an efficient alternative for exposing the
+underlying native Eigen types as opaque objects in a way that still integrates
+with NumPy and SciPy.
+
+.. [#f1] http://eigen.tuxfamily.org
+
+.. seealso::
+
+ The file :file:`example/eigen.cpp` contains a complete example that
+ shows how to pass Eigen sparse and dense data types in more detail.
+
Buffer protocol
===============
Python supports an extremely general and convenient approach for exchanging
-data between plugin libraries. Types can expose a buffer view [#f1]_,
-which provides fast direct access to the raw internal representation. Suppose
-we want to bind the following simplistic Matrix class:
+data between plugin libraries. Types can expose a buffer view [#f2]_, which
+provides fast direct access to the raw internal data representation. Suppose we
+want to bind the following simplistic Matrix class:
.. code-block:: cpp
@@ -856,16 +1000,19 @@ in a great variety of configurations, hence some safety checks are usually
necessary in the function body. Below, you can see an basic example on how to
define a custom constructor for the Eigen double precision matrix
(``Eigen::MatrixXd``) type, which supports initialization from compatible
-buffer
-objects (e.g. a NumPy matrix).
+buffer objects (e.g. a NumPy matrix).
.. code-block:: cpp
- py::class_<Eigen::MatrixXd>(m, "MatrixXd")
- .def("__init__", [](Eigen::MatrixXd &m, py::buffer b) {
+ /* Bind MatrixXd (or some other Eigen type) to Python */
+ typedef Eigen::MatrixXd Matrix;
+
+ typedef Matrix::Scalar Scalar;
+ constexpr bool rowMajor = Matrix::Flags & Eigen::RowMajorBit;
+
+ py::class_<Matrix>(m, "Matrix")
+ .def("__init__", [](Matrix &m, py::buffer b) {
typedef Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic> Strides;
- typedef Eigen::MatrixXd Matrix;
- typedef Matrix::Scalar Scalar;
/* Request a buffer descriptor from Python */
py::buffer_info info = b.request();
@@ -878,21 +1025,46 @@ objects (e.g. a NumPy matrix).
throw std::runtime_error("Incompatible buffer dimension!");
auto strides = Strides(
- info.strides[Matrix::Flags & Eigen::RowMajorBit ? 0 : 1] / sizeof(Scalar),
- info.strides[Matrix::Flags & Eigen::RowMajorBit ? 1 : 0] / sizeof(Scalar));
+ info.strides[rowMajor ? 0 : 1] / sizeof(Scalar),
+ info.strides[rowMajor ? 1 : 0] / sizeof(Scalar));
auto map = Eigen::Map<Matrix, 0, Strides>(
- (Scalar *) info.ptr, info.shape[0], info.shape[1], strides);
+ static_cat<Scalar *>(info.ptr), info.shape[0], info.shape[1], strides);
new (&m) Matrix(map);
});
+For reference, the ``def_buffer()`` call for this Eigen data type should look
+as follows:
+
+.. code-block:: cpp
+
+ .def_buffer([](Matrix &m) -> py::buffer_info {
+ return py::buffer_info(
+ m.data(), /* Pointer to buffer */
+ sizeof(Scalar), /* Size of one scalar */
+ /* Python struct-style format descriptor */
+ py::format_descriptor<Scalar>::value,
+ /* Number of dimensions */
+ 2,
+ /* Buffer dimensions */
+ { (size_t) m.rows(),
+ (size_t) m.cols() },
+ /* Strides (in bytes) for each index */
+ { sizeof(Scalar) * (rowMajor ? m.cols() : 1),
+ sizeof(Scalar) * (rowMajor ? 1 : m.rows()) }
+ );
+ })
+
+For a much easier approach of binding Eigen types (although with some
+limitations), refer to the section on :ref:`eigen`.
+
.. seealso::
The file :file:`example/example7.cpp` contains a complete example that
demonstrates using the buffer protocol with pybind11 in more detail.
-.. [#f1] http://docs.python.org/3/c-api/buffer.html
+.. [#f2] http://docs.python.org/3/c-api/buffer.html
NumPy support
=============
@@ -1199,105 +1371,6 @@ accessed by multiple extension modules:
};
-Treating STL data structures as opaque objects
-==============================================
-
-pybind11 heavily relies on a template matching mechanism to convert parameters
-and return values that are constructed from STL data types such as vectors,
-linked lists, hash tables, etc. This even works in a recursive manner, for
-instance to deal with lists of hash maps of pairs of elementary and custom
-types, etc.
-
-However, a fundamental limitation of this approach is that internal conversions
-between Python and C++ types involve a copy operation that prevents
-pass-by-reference semantics. What does this mean?
-
-Suppose we bind the following function
-
-.. code-block:: cpp
-
- void append_1(std::vector<int> &v) {
- v.push_back(1);
- }
-
-and call it from Python, the following happens:
-
-.. code-block:: python
-
- >>> v = [5, 6]
- >>> append_1(v)
- >>> print(v)
- [5, 6]
-
-As you can see, when passing STL data structures by reference, modifications
-are not propagated back the Python side. A similar situation arises when
-exposing STL data structures using the ``def_readwrite`` or ``def_readonly``
-functions:
-
-.. code-block:: cpp
-
- /* ... definition ... */
-
- class MyClass {
- std::vector<int> contents;
- };
-
- /* ... binding code ... */
-
- py::class_<MyClass>(m, "MyClass")
- .def(py::init<>)
- .def_readwrite("contents", &MyClass::contents);
-
-In this case, properties can be read and written in their entirety. However, an
-``append`` operaton involving such a list type has no effect:
-
-.. code-block:: python
-
- >>> m = MyClass()
- >>> m.contents = [5, 6]
- >>> print(m.contents)
- [5, 6]
- >>> m.contents.append(7)
- >>> print(m.contents)
- [5, 6]
-
-To deal with both of the above situations, pybind11 provides a macro named
-``PYBIND11_MAKE_OPAQUE(T)`` that disables the template-based conversion
-machinery of types, thus rendering them *opaque*. The contents of opaque
-objects are never inspected or extracted, hence they can be passed by
-reference. For instance, to turn ``std::vector<int>`` into an opaque type, add
-the declaration
-
-.. code-block:: cpp
-
- PYBIND11_MAKE_OPAQUE(std::vector<int>);
-
-before any binding code (e.g. invocations to ``class_::def()``, etc.). This
-macro must be specified at the top level, since instantiates a partial template
-overload. If your binding code consists of multiple compilation units, it must
-be present in every file preceding any usage of ``std::vector<int>``. Opaque
-types must also have a corresponding ``class_`` declaration to associate them
-with a name in Python, and to define a set of available operations:
-
-.. code-block:: cpp
-
- py::class_<std::vector<int>>(m, "IntVector")
- .def(py::init<>())
- .def("clear", &std::vector<int>::clear)
- .def("pop_back", &std::vector<int>::pop_back)
- .def("__len__", [](const std::vector<int> &v) { return v.size(); })
- .def("__iter__", [](std::vector<int> &v) {
- return py::make_iterator(v.begin(), v.end());
- }, py::keep_alive<0, 1>()) /* Keep vector alive while iterator is used */
- // ....
-
-
-.. seealso::
-
- The file :file:`example/example14.cpp` contains a complete example that
- demonstrates how to create and expose opaque types using pybind11 in more
- detail.
-
Pickling support
================
@@ -1320,7 +1393,7 @@ Suppose the class in question has the following signature:
int m_extra = 0;
};
-The binding code including the requisite ``__setstate__`` and ``__getstate__`` methods [#f2]_
+The binding code including the requisite ``__setstate__`` and ``__getstate__`` methods [#f3]_
looks as follows:
.. code-block:: cpp
@@ -1372,14 +1445,14 @@ memory corruption and/or segmentation faults.
The file :file:`example/example15.cpp` contains a complete example that
demonstrates how to pickle and unpickle types using pybind11 in more detail.
-.. [#f2] http://docs.python.org/3/library/pickle.html#pickling-class-instances
+.. [#f3] http://docs.python.org/3/library/pickle.html#pickling-class-instances
Generating documentation using Sphinx
=====================================
-Sphinx [#f3]_ has the ability to inspect the signatures and documentation
+Sphinx [#f4]_ has the ability to inspect the signatures and documentation
strings in pybind11-based extension modules to automatically generate beautiful
-documentation in a variety formats. The pbtest repository [#f4]_ contains a
+documentation in a variety formats. The pbtest repository [#f5]_ contains a
simple example repository which uses this approach.
There are two potential gotchas when using this approach: first, make sure that
@@ -1406,6 +1479,6 @@ work, it is important that all lines are indented consistently, i.e.:
----------
)mydelimiter");
-.. [#f3] http://www.sphinx-doc.org
-.. [#f4] http://github.com/pybind/pbtest
+.. [#f4] http://www.sphinx-doc.org
+.. [#f5] http://github.com/pybind/pbtest
diff --git a/docs/basics.rst b/docs/basics.rst
index cf39844..b1765c5 100644
--- a/docs/basics.rst
+++ b/docs/basics.rst
@@ -275,6 +275,10 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
+---------------------------------+--------------------------+-------------------------------+
| ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` |
+---------------------------------+--------------------------+-------------------------------+
+| ``Eigen::Matrix<...>`` | Dense Eigen matrices | :file:`pybind11/eigen.h` |
++---------------------------------+--------------------------+-------------------------------+
+| ``Eigen::SparseMatrix<...>`` | Sparse Eigen matrices | :file:`pybind11/eigen.h` |
++---------------------------------+--------------------------+-------------------------------+
.. [#f1] In practice, implementation and binding code will generally be located
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 103bd38..b5dee5f 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -5,6 +5,7 @@ Changelog
1.8 (Not yet released)
----------------------
+* Transparent conversion of sparse and dense Eigen data types
* Fixed incorrect default return value policy for functions returning a shared
pointer
* Don't allow casting a ``None`` value into a C++ lvalue reference
diff --git a/example/eigen.cpp b/example/eigen.cpp
new file mode 100644
index 0000000..b6fa24a
--- /dev/null
+++ b/example/eigen.cpp
@@ -0,0 +1,73 @@
+/*
+ example/eigen.cpp -- automatic conversion of Eigen types
+
+ Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+ All rights reserved. Use of this source code is governed by a
+ BSD-style license that can be found in the LICENSE file.
+*/
+
+#include "example.h"
+#include <pybind11/eigen.h>
+
+void init_eigen(py::module &m) {
+ typedef Eigen::Matrix<float, 5, 6, Eigen::RowMajor> FixedMatrixR;
+ typedef Eigen::Matrix<float, 5, 6> FixedMatrixC;
+ typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> DenseMatrixR;
+ typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> DenseMatrixC;
+ typedef Eigen::SparseMatrix<float, Eigen::RowMajor> SparseMatrixR;
+ typedef Eigen::SparseMatrix<float> SparseMatrixC;
+
+ // Non-symmetric matrix with zero elements
+ Eigen::MatrixXf mat(5, 6);
+ mat << 0, 3, 0, 0, 0, 11, 22, 0, 0, 0, 17, 11, 7, 5, 0, 1, 0, 11, 0,
+ 0, 0, 0, 0, 11, 0, 0, 14, 0, 8, 11;
+
+ m.def("fixed_r", [mat]() -> FixedMatrixR {
+ return FixedMatrixR(mat);
+ });
+
+ m.def("fixed_c", [mat]() -> FixedMatrixC {
+ return FixedMatrixC(mat);
+ });
+
+ m.def("fixed_passthrough_r", [](const FixedMatrixR &m) -> FixedMatrixR {
+ return m;
+ });
+
+ m.def("fixed_passthrough_c", [](const FixedMatrixC &m) -> FixedMatrixC {
+ return m;
+ });
+
+ m.def("dense_r", [mat]() -> DenseMatrixR {
+ return DenseMatrixR(mat);
+ });
+
+ m.def("dense_c", [mat]() -> DenseMatrixC {
+ return DenseMatrixC(mat);
+ });
+
+ m.def("dense_passthrough_r", [](const DenseMatrixR &m) -> DenseMatrixR {
+ return m;
+ });
+
+ m.def("dense_passthrough_c", [](const DenseMatrixC &m) -> DenseMatrixC {
+ return m;
+ });
+
+ m.def("sparse_r", [mat]() -> SparseMatrixR {
+ return Eigen::SparseView<Eigen::MatrixXf>(mat);
+ });
+
+ m.def("sparse_c", [mat]() -> SparseMatrixC {
+ return Eigen::SparseView<Eigen::MatrixXf>(mat);
+ });
+
+ m.def("sparse_passthrough_r", [](const SparseMatrixR &m) -> SparseMatrixR {
+ return m;
+ });
+
+ m.def("sparse_passthrough_c", [](const SparseMatrixC &m) -> SparseMatrixC {
+ return m;
+ });
+}
diff --git a/example/eigen.py b/example/eigen.py
new file mode 100644
index 0000000..accaf23
--- /dev/null
+++ b/example/eigen.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+from __future__ import print_function
+import sys
+sys.path.append('.')
+
+from example import fixed_r, fixed_c
+from example import fixed_passthrough_r, fixed_passthrough_c
+from example import dense_r, dense_c
+from example import dense_passthrough_r, dense_passthrough_c
+from example import sparse_r, sparse_c
+from example import sparse_passthrough_r, sparse_passthrough_c
+import numpy as np
+
+ref = np.array(
+ [[0, 3, 0, 0, 0, 11],
+ [22, 0, 0, 0, 17, 11],
+ [7, 5, 0, 1, 0, 11],
+ [0, 0, 0, 0, 0, 11],
+ [0, 0, 14, 0, 8, 11]])
+
+
+def check(mat):
+ return 'OK' if np.sum(mat - ref) == 0 else 'NOT OK'
+
+print("fixed_r = %s" % check(fixed_r()))
+print("fixed_c = %s" % check(fixed_c()))
+print("pt_r(fixed_r) = %s" % check(fixed_passthrough_r(fixed_r())))
+print("pt_c(fixed_c) = %s" % check(fixed_passthrough_c(fixed_c())))
+print("pt_r(fixed_c) = %s" % check(fixed_passthrough_r(fixed_c())))
+print("pt_c(fixed_r) = %s" % check(fixed_passthrough_c(fixed_r())))
+
+print("dense_r = %s" % check(dense_r()))
+print("dense_c = %s" % check(dense_c()))
+print("pt_r(dense_r) = %s" % check(dense_passthrough_r(dense_r())))
+print("pt_c(dense_c) = %s" % check(dense_passthrough_c(dense_c())))
+print("pt_r(dense_c) = %s" % check(dense_passthrough_r(dense_c())))
+print("pt_c(dense_r) = %s" % check(dense_passthrough_c(dense_r())))
+
+print("sparse_r = %s" % check(sparse_r()))
+print("sparse_c = %s" % check(sparse_c()))
+print("pt_r(sparse_r) = %s" % check(sparse_passthrough_r(sparse_r())))
+print("pt_c(sparse_c) = %s" % check(sparse_passthrough_c(sparse_c())))
+print("pt_r(sparse_c) = %s" % check(sparse_passthrough_r(sparse_c())))
+print("pt_c(sparse_r) = %s" % check(sparse_passthrough_c(sparse_r())))
diff --git a/example/eigen.ref b/example/eigen.ref
new file mode 100644
index 0000000..b87f8ed
--- /dev/null
+++ b/example/eigen.ref
@@ -0,0 +1,18 @@
+fixed_r = OK
+fixed_c = OK
+pt_r(fixed_r) = OK
+pt_c(fixed_c) = OK
+pt_r(fixed_c) = OK
+pt_c(fixed_r) = OK
+dense_r = OK
+dense_c = OK
+pt_r(dense_r) = OK
+pt_c(dense_c) = OK
+pt_r(dense_c) = OK
+pt_c(dense_r) = OK
+sparse_r = OK
+sparse_c = OK
+pt_r(sparse_r) = OK
+pt_c(sparse_c) = OK
+pt_r(sparse_c) = OK
+pt_c(sparse_r) = OK
diff --git a/example/example.cpp b/example/example.cpp
index 34b2df7..b4199e8 100644
--- a/example/example.cpp
+++ b/example/example.cpp
@@ -27,6 +27,10 @@ void init_ex15(py::module &);
void init_ex16(py::module &);
void init_issues(py::module &);
+#if defined(PYBIND11_TEST_EIGEN)
+ void init_eigen(py::module &);
+#endif
+
PYBIND11_PLUGIN(example) {
py::module m("example", "pybind example plugin");
@@ -48,5 +52,9 @@ PYBIND11_PLUGIN(example) {
init_ex16(m);
init_issues(m);
+ #if defined(PYBIND11_TEST_EIGEN)
+ init_eigen(m);
+ #endif
+
return m.ptr();
}
diff --git a/include/pybind11/eigen.h b/include/pybind11/eigen.h
new file mode 100644
index 0000000..f2f0985
--- /dev/null
+++ b/include/pybind11/eigen.h
@@ -0,0 +1,261 @@
+/*
+ pybind11/eigen.h: Transparent conversion for dense and sparse Eigen matrices
+
+ Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+ All rights reserved. Use of this source code is governed by a
+ BSD-style license that can be found in the LICENSE file.
+*/
+
+#pragma once
+
+#include "numpy.h"
+#include <Eigen/Core>
+#include <Eigen/SparseCore>
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
+#endif
+
+NAMESPACE_BEGIN(pybind11)
+NAMESPACE_BEGIN(detail)
+
+template <typename T> class is_eigen_dense {
+private:
+ template<typename Derived> static std::true_type test(const Eigen::DenseBase<Derived> &);
+ static std::false_type test(...);
+public:
+ static constexpr bool value = decltype(test(std::declval<T>()))::value;
+};
+
+template <typename T> class is_eigen_sparse {
+private:
+ template<typename Derived> static std::true_type test(const Eigen::SparseMatrixBase<Derived> &);
+ static std::false_type test(...);
+public:
+ static constexpr bool value = decltype(test(std::declval<T>()))::value;
+};
+
+template<typename Type>
+struct type_caster<Type, typename std::enable_if<is_eigen_dense<Type>::value>::type> {
+ typedef typename Type::Scalar Scalar;
+ static constexpr bool rowMajor = Type::Flags & Eigen::RowMajorBit;
+
+ bool load(handle src, bool) {
+ array_t<Scalar> buffer(src, true);
+ if (!buffer.check())
+ return false;
+
+ buffer_info info = buffer.request();
+ if (info.ndim == 1) {
+ typedef Eigen::Stride<Eigen::Dynamic, 0> Strides;
+ if (!Type::IsVectorAtCompileTime &&
+ !(Type::RowsAtCompileTime == Eigen::Dynamic &&
+ Type::ColsAtCompileTime == Eigen::Dynamic))
+ return false;
+
+ if (Type::SizeAtCompileTime != Eigen::Dynamic &&
+ info.shape[0] != (size_t) Type::SizeAtCompileTime)
+ return false;
+
+ auto strides = Strides(info.strides[0] / sizeof(Scalar), 0);
+
+ value = Eigen::Map<Type, 0, Strides>(
+ (Scalar *) info.ptr, info.shape[0], 1, strides);
+ } else if (info.ndim == 2) {
+ typedef Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic> Strides;
+
+ if ((Type::RowsAtCompileTime != Eigen::Dynamic && info.shape[0] != (size_t) Type::RowsAtCompileTime) ||
+ (Type::ColsAtCompileTime != Eigen::Dynamic && info.shape[1] != (size_t) Type::ColsAtCompileTime))
+ return false;
+
+ auto strides = Strides(
+ info.strides[rowMajor ? 0 : 1] / sizeof(Scalar),
+ info.strides[rowMajor ? 1 : 0] / sizeof(Scalar));
+
+ value = Eigen::Map<Type, 0, Strides>(
+ (Scalar *) info.ptr, info.shape[0], info.shape[1], strides);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ static handle cast(const Type *src, return_value_policy policy, handle parent) {
+ return cast(*src, policy, parent);
+ }
+
+ static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
+ array result(buffer_info(
+ /* Pointer to buffer */
+ const_cast<Scalar *>(src.data()),
+ /* Size of one scalar */
+ sizeof(Scalar),
+ /* Python struct-style format descriptor */
+ format_descriptor<Scalar>::value,
+ /* Number of dimensions */
+ 2,
+ /* Buffer dimensions */
+ { (size_t) src.rows(),
+ (size_t) src.cols() },
+ /* Strides (in bytes) for each index */
+ { sizeof(Scalar) * (rowMajor ? src.cols() : 1),
+ sizeof(Scalar) * (rowMajor ? 1 : src.rows()) }
+ ));
+ return result.release();
+ }
+
+ template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>;
+
+ static PYBIND11_DESCR name() {
+ return _("numpy.ndarray[dtype=") + npy_format_descriptor<Scalar>::name() +
+ _(", shape=(") + rows() + _(", ") + cols() + _(")]");
+ }
+
+ operator Type*() { return &value; }
+ operator Type&() { return value; }
+
+private:
+ template <typename T = Type, typename std::enable_if<T::RowsAtCompileTime == Eigen::Dynamic, int>::type = 0>
+ static PYBIND11_DESCR rows() { return _("m"); }
+ template <typename T = Type, typename std::enable_if<T::RowsAtCompileTime != Eigen::Dynamic, int>::type = 0>
+ static PYBIND11_DESCR rows() { return _<T::RowsAtCompileTime>(); }
+ template <typename T = Type, typename std::enable_if<T::ColsAtCompileTime == Eigen::Dynamic, int>::type = 0>
+ static PYBIND11_DESCR cols() { return _("n"); }
+ template <typename T = Type, typename std::enable_if<T::ColsAtCompileTime != Eigen::Dynamic, int>::type = 0>
+ static PYBIND11_DESCR cols() { return _<T::ColsAtCompileTime>(); }
+
+private:
+ Type value;
+};
+
+template<typename Type>
+struct type_caster<Type, typename std::enable_if<is_eigen_sparse<Type>::value>::type> {
+ typedef typename Type::Scalar Scalar;
+ typedef typename std::remove_reference<decltype(*std::declval<Type>().outerIndexPtr())>::type StorageIndex;
+ typedef typename Type::Index Index;
+ static constexpr bool rowMajor = Type::Flags & Eigen::RowMajorBit;
+
+ bool load(handle src, bool) {
+ object obj(src, true);
+ object sparse_module = module::import("scipy.sparse");
+ object matrix_type = sparse_module.attr(
+ rowMajor ? "csr_matrix" : "csc_matrix");
+
+ if (obj.get_type() != matrix_type.ptr()) {
+ try {
+ obj = matrix_type.call(obj);
+ } catch (const error_already_set &) {
+ PyErr_Clear();
+ return false;
+ }
+ }
+
+ auto valuesArray = array_t<Scalar>((object) obj.attr("data"));
+ auto innerIndicesArray = array_t<StorageIndex>((object) obj.attr("indices"));
+ auto outerIndicesArray = array_t<StorageIndex>((object) obj.attr("indptr"));
+ auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
+ auto nnz = obj.attr("nnz").cast<Index>();
+
+ if (!valuesArray.check() || !innerIndicesArray.check() ||
+ !outerIndicesArray.check())
+ return false;
+
+ buffer_info outerIndices = outerIndicesArray.request();
+ buffer_info innerIndices = innerIndicesArray.request();
+ buffer_info values = valuesArray.request();
+
+ value = Eigen::MappedSparseMatrix<Scalar, Type::Flags, StorageIndex>(
+ shape[0].cast<Index>(),
+ shape[1].cast<Index>(),
+ nnz,
+ static_cast<StorageIndex *>(outerIndices.ptr),
+ static_cast<StorageIndex *>(innerIndices.ptr),
+ static_cast<Scalar *>(values.ptr)
+ );
+
+ return true;
+ }
+
+ static handle cast(const Type *src, return_value_policy policy, handle parent) {
+ return cast(*src, policy, parent);
+ }
+
+ static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
+ const_cast<Type&>(src).makeCompressed();
+
+ object matrix_type = module::import("scipy.sparse").attr(
+ rowMajor ? "csr_matrix" : "csc_matrix");
+
+ array data(buffer_info(
+ // Pointer to buffer
+ const_cast<Scalar *>(src.valuePtr()),
+ // Size of one scalar
+ sizeof(Scalar),
+ // Python struct-style format descriptor
+ format_descriptor<Scalar>::value,
+ // Number of dimensions
+ 1,
+ // Buffer dimensions
+ { (size_t) src.nonZeros() },
+ // Strides
+ { sizeof(Scalar) }
+ ));
+
+ array outerIndices(buffer_info(
+ // Pointer to buffer
+ const_cast<StorageIndex *>(src.outerIndexPtr()),
+ // Size of one scalar
+ sizeof(StorageIndex),
+ // Python struct-style format descriptor
+ format_descriptor<StorageIndex>::value,
+ // Number of dimensions
+ 1,
+ // Buffer dimensions
+ { (size_t) (rowMajor ? src.rows() : src.cols()) + 1 },
+ // Strides
+ { sizeof(StorageIndex) }
+ ));
+
+ array innerIndices(buffer_info(
+ // Pointer to buffer
+ const_cast<StorageIndex *>(src.innerIndexPtr()),
+ // Size of one scalar
+ sizeof(StorageIndex),
+ // Python struct-style format descriptor
+ format_descriptor<StorageIndex>::value,
+ // Number of dimensions
+ 1,
+ // Buffer dimensions
+ { (size_t) src.nonZeros() },
+ // Strides
+ { sizeof(StorageIndex) }
+ ));
+
+ return matrix_type.call(
+ std::make_tuple(data, innerIndices, outerIndices),
+ std::make_pair(src.rows(), src.cols())
+ ).release();
+ }
+
+ template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>;
+
+ template <typename T = Type, typename std::enable_if<(T::Flags & Eigen::RowMajorBit) != 0, int>::type = 0>
+ static PYBIND11_DESCR name() { return _("scipy.sparse.csr_matrix[dtype=") + npy_format_descriptor<Scalar>::name() + _("]"); }
+ template <typename T = Type, typename std::enable_if<(T::Flags & Eigen::RowMajorBit) == 0, int>::type = 0>
+ static PYBIND11_DESCR name() { return _("scipy.sparse.csc_matrix[dtype=") + npy_format_descriptor<Scalar>::name() + _("]"); }
+
+ operator Type*() { return &value; }
+ operator Type&() { return value; }
+
+private:
+ Type value;
+};
+
+NAMESPACE_END(detail)
+NAMESPACE_END(pybind11)
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h
index 42d027a..b35790b 100644
--- a/include/pybind11/numpy.h
+++ b/include/pybind11/numpy.h
@@ -1,5 +1,5 @@
/*
- pybind11/numpy.h: Basic NumPy support, auto-vectorization support
+ pybind11/numpy.h: Basic NumPy support, vectorize() wrapper
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
@@ -20,8 +20,7 @@
#endif
NAMESPACE_BEGIN(pybind11)
-
-template <typename type, typename SFINAE = void> struct npy_format_descriptor { };
+namespace detail { template <typename type, typename SFINAE = void> struct npy_format_descriptor { }; }
class array : public buffer {
public:
@@ -84,7 +83,7 @@ public:
template <typename Type> array(size_t size, const Type *ptr) {
API& api = lookup_api();
- PyObject *descr = api.PyArray_DescrFromType_(npy_format_descriptor<Type>::value);
+ PyObject *descr = api.PyArray_DescrFromType_(detail::npy_format_descriptor<Type>::value);
if (descr == nullptr)
pybind11_fail("NumPy: unsupported buffer format!");
Py_intptr_t shape = (Py_intptr_t) size;
@@ -134,7 +133,7 @@ public:
if (ptr == nullptr)
return nullptr;
API &api = lookup_api();
- PyObject *descr = api.PyArray_DescrFromType_(npy_format_descriptor<T>::value);
+ PyObject *descr = api.PyArray_DescrFromType_(detail::npy_format_descriptor<T>::value);
PyObject *result = api.PyArray_FromAny_(
ptr, descr, 0, 0,
API::NPY_ENSURE_ARRAY_ | API::NPY_ARRAY_FORCECAST_ | ExtraFlags,
@@ -144,6 +143,8 @@ public:
}
};
+NAMESPACE_BEGIN(detail)
+
template <typename T> struct npy_format_descriptor<T, typename std::enable_if<std::is_integral<T>::value>::type> {
private:
constexpr static const int values[] = {
@@ -151,17 +152,21 @@ private:
array::API::NPY_INT_, array::API::NPY_UINT_, array::API::NPY_LONGLONG_, array::API::NPY_ULONGLONG_ };
public:
enum { value = values[detail::log2(sizeof(T)) * 2 + (std::is_unsigned<T>::value ? 1 : 0)] };
+ template <typename T2 = T, typename std::enable_if<std::is_signed<T2>::value, int>::type = 0>
+ static PYBIND11_DESCR name() { return _("int") + _<sizeof(T)*8>(); }
+ template <typename T2 = T, typename std::enable_if<!std::is_signed<T2>::value, int>::type = 0>
+ static PYBIND11_DESCR name() { return _("uint") + _<sizeof(T)*8>(); }
};
template <typename T> constexpr const int npy_format_descriptor<
T, typename std::enable_if<std::is_integral<T>::value>::type>::values[8];
-#define DECL_FMT(t, n) template<> struct npy_format_descriptor<t> { enum { value = array::API::n }; }
-DECL_FMT(float, NPY_FLOAT_); DECL_FMT(double, NPY_DOUBLE_); DECL_FMT(bool, NPY_BOOL_);
-DECL_FMT(std::complex<float>, NPY_CFLOAT_); DECL_FMT(std::complex<double>, NPY_CDOUBLE_);
+#define DECL_FMT(Type, NumPyName, Name) template<> struct npy_format_descriptor<Type> { \
+ enum { value = array::API::NumPyName }; \
+ static PYBIND11_DESCR name() { return _(Name); } }
+DECL_FMT(float, NPY_FLOAT_, "float32"); DECL_FMT(double, NPY_DOUBLE_, "float64"); DECL_FMT(bool, NPY_BOOL_, "bool");
+DECL_FMT(std::complex<float>, NPY_CFLOAT_, "complex64"); DECL_FMT(std::complex<double>, NPY_CDOUBLE_, "complex128");
#undef DECL_FMT
-NAMESPACE_BEGIN(detail)
-
template <class T>
using array_iterator = typename std::add_pointer<T>::type;
@@ -348,7 +353,7 @@ struct vectorize_helper {
buffer_info buf = result.request();
Return *output = (Return *) buf.ptr;
- if(trivial_broadcast) {
+ if (trivial_broadcast) {
/* Call the function */
for (size_t i=0; i<size; ++i) {
output[i] = f((buffers[Index].size == 1
@@ -379,7 +384,7 @@ struct vectorize_helper {
};
template <typename T> struct handle_type_name<array_t<T>> {
- static PYBIND11_DESCR name() { return _("array[") + type_caster<T>::name() + _("]"); }
+ static PYBIND11_DESCR name() { return _("numpy.ndarray[dtype=") + type_caster<T>::name() + _("]"); }
};
NAMESPACE_END(detail)
diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h
index 39adc7e..e0177de 100644
--- a/include/pybind11/stl.h
+++ b/include/pybind11/stl.h
@@ -1,5 +1,5 @@
/*
- pybind11/complex.h: Complex number support
+ pybind11/stl.h: Transparent conversion for STL data types
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
diff --git a/setup.py b/setup.py
index 7562860..4c6e156 100644
--- a/setup.py
+++ b/setup.py
@@ -20,6 +20,7 @@ setup(
'include/pybind11/cast.h',
'include/pybind11/complex.h',
'include/pybind11/descr.h',
+ 'include/pybind11/eigen.h',
'include/pybind11/numpy.h',
'include/pybind11/pybind11.h',
'include/pybind11/stl.h',
diff --git a/tools/FindEigen3.cmake b/tools/FindEigen3.cmake
new file mode 100644
index 0000000..9c546a0
--- /dev/null
+++ b/tools/FindEigen3.cmake
@@ -0,0 +1,81 @@
+# - Try to find Eigen3 lib
+#
+# This module supports requiring a minimum version, e.g. you can do
+# find_package(Eigen3 3.1.2)
+# to require version 3.1.2 or newer of Eigen3.
+#
+# Once done this will define
+#
+# EIGEN3_FOUND - system has eigen lib with correct version
+# EIGEN3_INCLUDE_DIR - the eigen include directory
+# EIGEN3_VERSION - eigen version
+
+# Copyright (c) 2006, 2007 Montel Laurent, <montel@kde.org>
+# Copyright (c) 2008, 2009 Gael Guennebaud, <g.gael@free.fr>
+# Copyright (c) 2009 Benoit Jacob <jacob.benoit.1@gmail.com>
+# Redistribution and use is allowed according to the terms of the 2-clause BSD license.
+
+if(NOT Eigen3_FIND_VERSION)
+ if(NOT Eigen3_FIND_VERSION_MAJOR)
+ set(Eigen3_FIND_VERSION_MAJOR 2)
+ endif(NOT Eigen3_FIND_VERSION_MAJOR)
+ if(NOT Eigen3_FIND_VERSION_MINOR)
+ set(Eigen3_FIND_VERSION_MINOR 91)
+ endif(NOT Eigen3_FIND_VERSION_MINOR)
+ if(NOT Eigen3_FIND_VERSION_PATCH)
+ set(Eigen3_FIND_VERSION_PATCH 0)
+ endif(NOT Eigen3_FIND_VERSION_PATCH)
+
+ set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}")
+endif(NOT Eigen3_FIND_VERSION)
+
+macro(_eigen3_check_version)
+ file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)
+
+ string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}")
+ set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}")
+ string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}")
+ set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}")
+ string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}")
+ set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}")
+
+ set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION})
+ if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
+ set(EIGEN3_VERSION_OK FALSE)
+ else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
+ set(EIGEN3_VERSION_OK TRUE)
+ endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
+
+ if(NOT EIGEN3_VERSION_OK)
+
+ message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, "
+ "but at least version ${Eigen3_FIND_VERSION} is required")
+ endif(NOT EIGEN3_VERSION_OK)
+endmacro(_eigen3_check_version)
+
+if (EIGEN3_INCLUDE_DIR)
+
+ # in cache already
+ _eigen3_check_version()
+ set(EIGEN3_FOUND ${EIGEN3_VERSION_OK})
+
+else (EIGEN3_INCLUDE_DIR)
+
+ find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
+ PATHS
+ ${CMAKE_INSTALL_PREFIX}/include
+ ${KDE4_INCLUDE_DIR}
+ PATH_SUFFIXES eigen3 eigen
+ )
+
+ if(EIGEN3_INCLUDE_DIR)
+ _eigen3_check_version()
+ endif(EIGEN3_INCLUDE_DIR)
+
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK)
+
+ mark_as_advanced(EIGEN3_INCLUDE_DIR)
+
+endif(EIGEN3_INCLUDE_DIR)
+