diff options
author | Wenzel Jakob <wenzel.jakob@epfl.ch> | 2016-05-05 20:33:54 +0200 |
---|---|---|
committer | Wenzel Jakob <wenzel.jakob@epfl.ch> | 2016-05-05 21:44:29 +0200 |
commit | 9e0a0568fed8c05ba7bb8d5101af7b6c407fb4eb (patch) | |
tree | 74164202b9353c27ef6cc9eba7914b7e95fcef72 | |
parent | 9ac5bc55314457e69dcffd352e79c1332f454e25 (diff) | |
download | platform_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.txt | 15 | ||||
-rw-r--r-- | docs/advanced.rst | 309 | ||||
-rw-r--r-- | docs/basics.rst | 4 | ||||
-rw-r--r-- | docs/changelog.rst | 1 | ||||
-rw-r--r-- | example/eigen.cpp | 73 | ||||
-rw-r--r-- | example/eigen.py | 44 | ||||
-rw-r--r-- | example/eigen.ref | 18 | ||||
-rw-r--r-- | example/example.cpp | 8 | ||||
-rw-r--r-- | include/pybind11/eigen.h | 261 | ||||
-rw-r--r-- | include/pybind11/numpy.h | 29 | ||||
-rw-r--r-- | include/pybind11/stl.h | 2 | ||||
-rw-r--r-- | setup.py | 1 | ||||
-rw-r--r-- | tools/FindEigen3.cmake | 81 |
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> @@ -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) + |