Blame external/pybind11/tests/test_eigen.cpp

Packit 534379
/*
Packit 534379
    tests/eigen.cpp -- automatic conversion of Eigen types
Packit 534379
Packit 534379
    Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
Packit 534379
Packit 534379
    All rights reserved. Use of this source code is governed by a
Packit 534379
    BSD-style license that can be found in the LICENSE file.
Packit 534379
*/
Packit 534379
Packit 534379
#include "pybind11_tests.h"
Packit 534379
#include "constructor_stats.h"
Packit 534379
#include <pybind11/eigen.h>
Packit 534379
#include <pybind11/stl.h>
Packit 534379
Packit 534379
#if defined(_MSC_VER)
Packit 534379
#  pragma warning(disable: 4996) // C4996: std::unary_negation is deprecated
Packit 534379
#endif
Packit 534379
Packit 534379
#include <Eigen/Cholesky>
Packit 534379
Packit 534379
using MatrixXdR = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
Packit 534379
Packit 534379
Packit 534379
Packit 534379
// Sets/resets a testing reference matrix to have values of 10*r + c, where r and c are the
Packit 534379
// (1-based) row/column number.
Packit 534379
template <typename M> void reset_ref(M &x) {
Packit 534379
    for (int i = 0; i < x.rows(); i++) for (int j = 0; j < x.cols(); j++)
Packit 534379
        x(i, j) = 11 + 10*i + j;
Packit 534379
}
Packit 534379
Packit 534379
// Returns a static, column-major matrix
Packit 534379
Eigen::MatrixXd &get_cm() {
Packit 534379
    static Eigen::MatrixXd *x;
Packit 534379
    if (!x) {
Packit 534379
        x = new Eigen::MatrixXd(3, 3);
Packit 534379
        reset_ref(*x);
Packit 534379
    }
Packit 534379
    return *x;
Packit 534379
}
Packit 534379
// Likewise, but row-major
Packit 534379
MatrixXdR &get_rm() {
Packit 534379
    static MatrixXdR *x;
Packit 534379
    if (!x) {
Packit 534379
        x = new MatrixXdR(3, 3);
Packit 534379
        reset_ref(*x);
Packit 534379
    }
Packit 534379
    return *x;
Packit 534379
}
Packit 534379
// Resets the values of the static matrices returned by get_cm()/get_rm()
Packit 534379
void reset_refs() {
Packit 534379
    reset_ref(get_cm());
Packit 534379
    reset_ref(get_rm());
Packit 534379
}
Packit 534379
Packit 534379
// Returns element 2,1 from a matrix (used to test copy/nocopy)
Packit 534379
double get_elem(Eigen::Ref<const Eigen::MatrixXd> m) { return m(2, 1); };
Packit 534379
Packit 534379
Packit 534379
// Returns a matrix with 10*r + 100*c added to each matrix element (to help test that the matrix
Packit 534379
// reference is referencing rows/columns correctly).
Packit 534379
template <typename MatrixArgType> Eigen::MatrixXd adjust_matrix(MatrixArgType m) {
Packit 534379
    Eigen::MatrixXd ret(m);
Packit 534379
    for (int c = 0; c < m.cols(); c++) for (int r = 0; r < m.rows(); r++)
Packit 534379
        ret(r, c) += 10*r + 100*c;
Packit 534379
    return ret;
Packit 534379
}
Packit 534379
Packit 534379
struct CustomOperatorNew {
Packit 534379
    CustomOperatorNew() = default;
Packit 534379
Packit 534379
    Eigen::Matrix4d a = Eigen::Matrix4d::Zero();
Packit 534379
    Eigen::Matrix4d b = Eigen::Matrix4d::Identity();
Packit 534379
Packit 534379
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
Packit 534379
};
Packit 534379
Packit 534379
TEST_SUBMODULE(eigen, m) {
Packit 534379
    using FixedMatrixR = Eigen::Matrix<float, 5, 6, Eigen::RowMajor>;
Packit 534379
    using FixedMatrixC = Eigen::Matrix<float, 5, 6>;
Packit 534379
    using DenseMatrixR = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
Packit 534379
    using DenseMatrixC = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>;
Packit 534379
    using FourRowMatrixC = Eigen::Matrix<float, 4, Eigen::Dynamic>;
Packit 534379
    using FourColMatrixC = Eigen::Matrix<float, Eigen::Dynamic, 4>;
Packit 534379
    using FourRowMatrixR = Eigen::Matrix<float, 4, Eigen::Dynamic>;
Packit 534379
    using FourColMatrixR = Eigen::Matrix<float, Eigen::Dynamic, 4>;
Packit 534379
    using SparseMatrixR = Eigen::SparseMatrix<float, Eigen::RowMajor>;
Packit 534379
    using SparseMatrixC = Eigen::SparseMatrix<float>;
Packit 534379
Packit 534379
    m.attr("have_eigen") = true;
Packit 534379
Packit 534379
    // various tests
Packit 534379
    m.def("double_col", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return 2.0f * x; });
Packit 534379
    m.def("double_row", [](const Eigen::RowVectorXf &x) -> Eigen::RowVectorXf { return 2.0f * x; });
Packit 534379
    m.def("double_complex", [](const Eigen::VectorXcf &x) -> Eigen::VectorXcf { return 2.0f * x; });
Packit 534379
    m.def("double_threec", [](py::EigenDRef<Eigen::Vector3f> x) { x *= 2; });
Packit 534379
    m.def("double_threer", [](py::EigenDRef<Eigen::RowVector3f> x) { x *= 2; });
Packit 534379
    m.def("double_mat_cm", [](Eigen::MatrixXf x) -> Eigen::MatrixXf { return 2.0f * x; });
Packit 534379
    m.def("double_mat_rm", [](DenseMatrixR x) -> DenseMatrixR { return 2.0f * x; });
Packit 534379
Packit 534379
    // test_eigen_ref_to_python
Packit 534379
    // Different ways of passing via Eigen::Ref; the first and second are the Eigen-recommended
Packit 534379
    m.def("cholesky1", [](Eigen::Ref<MatrixXdR> x) -> Eigen::MatrixXd { return x.llt().matrixL(); });
Packit 534379
    m.def("cholesky2", [](const Eigen::Ref<const MatrixXdR> &x) -> Eigen::MatrixXd { return x.llt().matrixL(); });
Packit 534379
    m.def("cholesky3", [](const Eigen::Ref<MatrixXdR> &x) -> Eigen::MatrixXd { return x.llt().matrixL(); });
Packit 534379
    m.def("cholesky4", [](Eigen::Ref<const MatrixXdR> x) -> Eigen::MatrixXd { return x.llt().matrixL(); });
Packit 534379
Packit 534379
    // test_eigen_ref_mutators
Packit 534379
    // Mutators: these add some value to the given element using Eigen, but Eigen should be mapping into
Packit 534379
    // the numpy array data and so the result should show up there.  There are three versions: one that
Packit 534379
    // works on a contiguous-row matrix (numpy's default), one for a contiguous-column matrix, and one
Packit 534379
    // for any matrix.
Packit 534379
    auto add_rm = [](Eigen::Ref<MatrixXdR> x, int r, int c, double v) { x(r,c) += v; };
Packit 534379
    auto add_cm = [](Eigen::Ref<Eigen::MatrixXd> x, int r, int c, double v) { x(r,c) += v; };
Packit 534379
Packit 534379
    // Mutators (Eigen maps into numpy variables):
Packit 534379
    m.def("add_rm", add_rm); // Only takes row-contiguous
Packit 534379
    m.def("add_cm", add_cm); // Only takes column-contiguous
Packit 534379
    // Overloaded versions that will accept either row or column contiguous:
Packit 534379
    m.def("add1", add_rm);
Packit 534379
    m.def("add1", add_cm);
Packit 534379
    m.def("add2", add_cm);
Packit 534379
    m.def("add2", add_rm);
Packit 534379
    // This one accepts a matrix of any stride:
Packit 534379
    m.def("add_any", [](py::EigenDRef<Eigen::MatrixXd> x, int r, int c, double v) { x(r,c) += v; });
Packit 534379
Packit 534379
    // Return mutable references (numpy maps into eigen variables)
Packit 534379
    m.def("get_cm_ref", []() { return Eigen::Ref<Eigen::MatrixXd>(get_cm()); });
Packit 534379
    m.def("get_rm_ref", []() { return Eigen::Ref<MatrixXdR>(get_rm()); });
Packit 534379
    // The same references, but non-mutable (numpy maps into eigen variables, but is !writeable)
Packit 534379
    m.def("get_cm_const_ref", []() { return Eigen::Ref<const Eigen::MatrixXd>(get_cm()); });
Packit 534379
    m.def("get_rm_const_ref", []() { return Eigen::Ref<const MatrixXdR>(get_rm()); });
Packit 534379
Packit 534379
    m.def("reset_refs", reset_refs); // Restores get_{cm,rm}_ref to original values
Packit 534379
Packit 534379
    // Increments and returns ref to (same) matrix
Packit 534379
    m.def("incr_matrix", [](Eigen::Ref<Eigen::MatrixXd> m, double v) {
Packit 534379
        m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v);
Packit 534379
        return m;
Packit 534379
    }, py::return_value_policy::reference);
Packit 534379
Packit 534379
    // Same, but accepts a matrix of any strides
Packit 534379
    m.def("incr_matrix_any", [](py::EigenDRef<Eigen::MatrixXd> m, double v) {
Packit 534379
        m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v);
Packit 534379
        return m;
Packit 534379
    }, py::return_value_policy::reference);
Packit 534379
Packit 534379
    // Returns an eigen slice of even rows
Packit 534379
    m.def("even_rows", [](py::EigenDRef<Eigen::MatrixXd> m) {
Packit 534379
        return py::EigenDMap<Eigen::MatrixXd>(
Packit 534379
                m.data(), (m.rows() + 1) / 2, m.cols(),
Packit 534379
                py::EigenDStride(m.outerStride(), 2 * m.innerStride()));
Packit 534379
    }, py::return_value_policy::reference);
Packit 534379
Packit 534379
    // Returns an eigen slice of even columns
Packit 534379
    m.def("even_cols", [](py::EigenDRef<Eigen::MatrixXd> m) {
Packit 534379
        return py::EigenDMap<Eigen::MatrixXd>(
Packit 534379
                m.data(), m.rows(), (m.cols() + 1) / 2,
Packit 534379
                py::EigenDStride(2 * m.outerStride(), m.innerStride()));
Packit 534379
    }, py::return_value_policy::reference);
Packit 534379
Packit 534379
    // Returns diagonals: a vector-like object with an inner stride != 1
Packit 534379
    m.def("diagonal", [](const Eigen::Ref<const Eigen::MatrixXd> &x) { return x.diagonal(); });
Packit 534379
    m.def("diagonal_1", [](const Eigen::Ref<const Eigen::MatrixXd> &x) { return x.diagonal<1>(); });
Packit 534379
    m.def("diagonal_n", [](const Eigen::Ref<const Eigen::MatrixXd> &x, int index) { return x.diagonal(index); });
Packit 534379
Packit 534379
    // Return a block of a matrix (gives non-standard strides)
Packit 534379
    m.def("block", [](const Eigen::Ref<const Eigen::MatrixXd> &x, int start_row, int start_col, int block_rows, int block_cols) {
Packit 534379
        return x.block(start_row, start_col, block_rows, block_cols);
Packit 534379
    });
Packit 534379
Packit 534379
    // test_eigen_return_references, test_eigen_keepalive
Packit 534379
    // return value referencing/copying tests:
Packit 534379
    class ReturnTester {
Packit 534379
        Eigen::MatrixXd mat = create();
Packit 534379
    public:
Packit 534379
        ReturnTester() { print_created(this); }
Packit 534379
        ~ReturnTester() { print_destroyed(this); }
Packit 534379
        static Eigen::MatrixXd create() { return Eigen::MatrixXd::Ones(10, 10); }
Packit 534379
        static const Eigen::MatrixXd createConst() { return Eigen::MatrixXd::Ones(10, 10); }
Packit 534379
        Eigen::MatrixXd &get() { return mat; }
Packit 534379
        Eigen::MatrixXd *getPtr() { return &mat; }
Packit 534379
        const Eigen::MatrixXd &view() { return mat; }
Packit 534379
        const Eigen::MatrixXd *viewPtr() { return &mat; }
Packit 534379
        Eigen::Ref<Eigen::MatrixXd> ref() { return mat; }
Packit 534379
        Eigen::Ref<const Eigen::MatrixXd> refConst() { return mat; }
Packit 534379
        Eigen::Block<Eigen::MatrixXd> block(int r, int c, int nrow, int ncol) { return mat.block(r, c, nrow, ncol); }
Packit 534379
        Eigen::Block<const Eigen::MatrixXd> blockConst(int r, int c, int nrow, int ncol) const { return mat.block(r, c, nrow, ncol); }
Packit 534379
        py::EigenDMap<Eigen::Matrix2d> corners() { return py::EigenDMap<Eigen::Matrix2d>(mat.data(),
Packit 534379
                    py::EigenDStride(mat.outerStride() * (mat.outerSize()-1), mat.innerStride() * (mat.innerSize()-1))); }
Packit 534379
        py::EigenDMap<const Eigen::Matrix2d> cornersConst() const { return py::EigenDMap<const Eigen::Matrix2d>(mat.data(),
Packit 534379
                    py::EigenDStride(mat.outerStride() * (mat.outerSize()-1), mat.innerStride() * (mat.innerSize()-1))); }
Packit 534379
    };
Packit 534379
    using rvp = py::return_value_policy;
Packit 534379
    py::class_<ReturnTester>(m, "ReturnTester")
Packit 534379
        .def(py::init<>())
Packit 534379
        .def_static("create", &ReturnTester::create)
Packit 534379
        .def_static("create_const", &ReturnTester::createConst)
Packit 534379
        .def("get", &ReturnTester::get, rvp::reference_internal)
Packit 534379
        .def("get_ptr", &ReturnTester::getPtr, rvp::reference_internal)
Packit 534379
        .def("view", &ReturnTester::view, rvp::reference_internal)
Packit 534379
        .def("view_ptr", &ReturnTester::view, rvp::reference_internal)
Packit 534379
        .def("copy_get", &ReturnTester::get)   // Default rvp: copy
Packit 534379
        .def("copy_view", &ReturnTester::view) //         "
Packit 534379
        .def("ref", &ReturnTester::ref) // Default for Ref is to reference
Packit 534379
        .def("ref_const", &ReturnTester::refConst) // Likewise, but const
Packit 534379
        .def("ref_safe", &ReturnTester::ref, rvp::reference_internal)
Packit 534379
        .def("ref_const_safe", &ReturnTester::refConst, rvp::reference_internal)
Packit 534379
        .def("copy_ref", &ReturnTester::ref, rvp::copy)
Packit 534379
        .def("copy_ref_const", &ReturnTester::refConst, rvp::copy)
Packit 534379
        .def("block", &ReturnTester::block)
Packit 534379
        .def("block_safe", &ReturnTester::block, rvp::reference_internal)
Packit 534379
        .def("block_const", &ReturnTester::blockConst, rvp::reference_internal)
Packit 534379
        .def("copy_block", &ReturnTester::block, rvp::copy)
Packit 534379
        .def("corners", &ReturnTester::corners, rvp::reference_internal)
Packit 534379
        .def("corners_const", &ReturnTester::cornersConst, rvp::reference_internal)
Packit 534379
        ;
Packit 534379
Packit 534379
    // test_special_matrix_objects
Packit 534379
    // Returns a DiagonalMatrix with diagonal (1,2,3,...)
Packit 534379
    m.def("incr_diag", [](int k) {
Packit 534379
        Eigen::DiagonalMatrix<int, Eigen::Dynamic> m(k);
Packit 534379
        for (int i = 0; i < k; i++) m.diagonal()[i] = i+1;
Packit 534379
        return m;
Packit 534379
    });
Packit 534379
Packit 534379
    // Returns a SelfAdjointView referencing the lower triangle of m
Packit 534379
    m.def("symmetric_lower", [](const Eigen::MatrixXi &m) {
Packit 534379
            return m.selfadjointView<Eigen::Lower>();
Packit 534379
    });
Packit 534379
    // Returns a SelfAdjointView referencing the lower triangle of m
Packit 534379
    m.def("symmetric_upper", [](const Eigen::MatrixXi &m) {
Packit 534379
            return m.selfadjointView<Eigen::Upper>();
Packit 534379
    });
Packit 534379
Packit 534379
    // Test matrix for various functions below.
Packit 534379
    Eigen::MatrixXf mat(5, 6);
Packit 534379
    mat << 0,  3,  0,  0,  0, 11,
Packit 534379
           22, 0,  0,  0, 17, 11,
Packit 534379
           7,  5,  0,  1,  0, 11,
Packit 534379
           0,  0,  0,  0,  0, 11,
Packit 534379
           0,  0, 14,  0,  8, 11;
Packit 534379
Packit 534379
    // test_fixed, and various other tests
Packit 534379
    m.def("fixed_r", [mat]() -> FixedMatrixR { return FixedMatrixR(mat); });
Packit 534379
    m.def("fixed_r_const", [mat]() -> const FixedMatrixR { return FixedMatrixR(mat); });
Packit 534379
    m.def("fixed_c", [mat]() -> FixedMatrixC { return FixedMatrixC(mat); });
Packit 534379
    m.def("fixed_copy_r", [](const FixedMatrixR &m) -> FixedMatrixR { return m; });
Packit 534379
    m.def("fixed_copy_c", [](const FixedMatrixC &m) -> FixedMatrixC { return m; });
Packit 534379
    // test_mutator_descriptors
Packit 534379
    m.def("fixed_mutator_r", [](Eigen::Ref<FixedMatrixR>) {});
Packit 534379
    m.def("fixed_mutator_c", [](Eigen::Ref<FixedMatrixC>) {});
Packit 534379
    m.def("fixed_mutator_a", [](py::EigenDRef<FixedMatrixC>) {});
Packit 534379
    // test_dense
Packit 534379
    m.def("dense_r", [mat]() -> DenseMatrixR { return DenseMatrixR(mat); });
Packit 534379
    m.def("dense_c", [mat]() -> DenseMatrixC { return DenseMatrixC(mat); });
Packit 534379
    m.def("dense_copy_r", [](const DenseMatrixR &m) -> DenseMatrixR { return m; });
Packit 534379
    m.def("dense_copy_c", [](const DenseMatrixC &m) -> DenseMatrixC { return m; });
Packit 534379
    // test_sparse, test_sparse_signature
Packit 534379
    m.def("sparse_r", [mat]() -> SparseMatrixR { return Eigen::SparseView<Eigen::MatrixXf>(mat); });
Packit 534379
    m.def("sparse_c", [mat]() -> SparseMatrixC { return Eigen::SparseView<Eigen::MatrixXf>(mat); });
Packit 534379
    m.def("sparse_copy_r", [](const SparseMatrixR &m) -> SparseMatrixR { return m; });
Packit 534379
    m.def("sparse_copy_c", [](const SparseMatrixC &m) -> SparseMatrixC { return m; });
Packit 534379
    // test_partially_fixed
Packit 534379
    m.def("partial_copy_four_rm_r", [](const FourRowMatrixR &m) -> FourRowMatrixR { return m; });
Packit 534379
    m.def("partial_copy_four_rm_c", [](const FourColMatrixR &m) -> FourColMatrixR { return m; });
Packit 534379
    m.def("partial_copy_four_cm_r", [](const FourRowMatrixC &m) -> FourRowMatrixC { return m; });
Packit 534379
    m.def("partial_copy_four_cm_c", [](const FourColMatrixC &m) -> FourColMatrixC { return m; });
Packit 534379
Packit 534379
    // test_cpp_casting
Packit 534379
    // Test that we can cast a numpy object to a Eigen::MatrixXd explicitly
Packit 534379
    m.def("cpp_copy", [](py::handle m) { return m.cast<Eigen::MatrixXd>()(1, 0); });
Packit 534379
    m.def("cpp_ref_c", [](py::handle m) { return m.cast<Eigen::Ref<Eigen::MatrixXd>>()(1, 0); });
Packit 534379
    m.def("cpp_ref_r", [](py::handle m) { return m.cast<Eigen::Ref<MatrixXdR>>()(1, 0); });
Packit 534379
    m.def("cpp_ref_any", [](py::handle m) { return m.cast<py::EigenDRef<Eigen::MatrixXd>>()(1, 0); });
Packit 534379
Packit 534379
Packit 534379
    // test_nocopy_wrapper
Packit 534379
    // Test that we can prevent copying into an argument that would normally copy: First a version
Packit 534379
    // that would allow copying (if types or strides don't match) for comparison:
Packit 534379
    m.def("get_elem", &get_elem);
Packit 534379
    // Now this alternative that calls the tells pybind to fail rather than copy:
Packit 534379
    m.def("get_elem_nocopy", [](Eigen::Ref<const Eigen::MatrixXd> m) -> double { return get_elem(m); },
Packit 534379
            py::arg().noconvert());
Packit 534379
    // Also test a row-major-only no-copy const ref:
Packit 534379
    m.def("get_elem_rm_nocopy", [](Eigen::Ref<const Eigen::Matrix<long, -1, -1, Eigen::RowMajor>> &m) -> long { return m(2, 1); },
Packit 534379
            py::arg().noconvert());
Packit 534379
Packit 534379
    // test_issue738
Packit 534379
    // Issue #738: 1xN or Nx1 2D matrices were neither accepted nor properly copied with an
Packit 534379
    // incompatible stride value on the length-1 dimension--but that should be allowed (without
Packit 534379
    // requiring a copy!) because the stride value can be safely ignored on a size-1 dimension.
Packit 534379
    m.def("iss738_f1", &adjust_matrix<const Eigen::Ref<const Eigen::MatrixXd> &>, py::arg().noconvert());
Packit 534379
    m.def("iss738_f2", &adjust_matrix<const Eigen::Ref<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>> &>, py::arg().noconvert());
Packit 534379
Packit 534379
    // test_issue1105
Packit 534379
    // Issue #1105: when converting from a numpy two-dimensional (Nx1) or (1xN) value into a dense
Packit 534379
    // eigen Vector or RowVector, the argument would fail to load because the numpy copy would fail:
Packit 534379
    // numpy won't broadcast a Nx1 into a 1-dimensional vector.
Packit 534379
    m.def("iss1105_col", [](Eigen::VectorXd) { return true; });
Packit 534379
    m.def("iss1105_row", [](Eigen::RowVectorXd) { return true; });
Packit 534379
Packit 534379
    // test_named_arguments
Packit 534379
    // Make sure named arguments are working properly:
Packit 534379
    m.def("matrix_multiply", [](const py::EigenDRef<const Eigen::MatrixXd> A, const py::EigenDRef<const Eigen::MatrixXd> B)
Packit 534379
            -> Eigen::MatrixXd {
Packit 534379
        if (A.cols() != B.rows()) throw std::domain_error("Nonconformable matrices!");
Packit 534379
        return A * B;
Packit 534379
    }, py::arg("A"), py::arg("B"));
Packit 534379
Packit 534379
    // test_custom_operator_new
Packit 534379
    py::class_<CustomOperatorNew>(m, "CustomOperatorNew")
Packit 534379
        .def(py::init<>())
Packit 534379
        .def_readonly("a", &CustomOperatorNew::a)
Packit 534379
        .def_readonly("b", &CustomOperatorNew::b);
Packit 534379
Packit 534379
    // test_eigen_ref_life_support
Packit 534379
    // In case of a failure (the caster's temp array does not live long enough), creating
Packit 534379
    // a new array (np.ones(10)) increases the chances that the temp array will be garbage
Packit 534379
    // collected and/or that its memory will be overridden with different values.
Packit 534379
    m.def("get_elem_direct", [](Eigen::Ref<const Eigen::VectorXd> v) {
Packit 534379
        py::module::import("numpy").attr("ones")(10);
Packit 534379
        return v(5);
Packit 534379
    });
Packit 534379
    m.def("get_elem_indirect", [](std::vector<Eigen::Ref<const Eigen::VectorXd>> v) {
Packit 534379
        py::module::import("numpy").attr("ones")(10);
Packit 534379
        return v[0](5);
Packit 534379
    });
Packit 534379
}