transparent conversion of dense and sparse Eigen types

This commit is contained in:
Wenzel Jakob 2016-05-05 20:33:54 +02:00
parent 9ac5bc5531
commit 9e0a0568fe
13 changed files with 715 additions and 131 deletions

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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

73
example/eigen.cpp Normal file
View File

@ -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;
});
}

44
example/eigen.py Normal file
View File

@ -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())))

18
example/eigen.ref Normal file
View File

@ -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

View File

@ -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();
}

261
include/pybind11/eigen.h Normal file
View File

@ -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

View File

@ -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)

View File

@ -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>

View File

@ -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',

81
tools/FindEigen3.cmake Normal file
View File

@ -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)