Skip to content

Commit 6a81dbb

Browse files
committed
Fix 2D Nx1/1xN inputs to eigen dense vector args
This fixes a bug introduced in b68959e when passing in a two-dimensional, but conformable, array as the value for a compile-time Eigen vector (such as VectorXd or RowVectorXd). The commit switched to using numpy to copy into the eigen data, but this broke the described case because numpy refuses to broadcast a (N,1) into a (N). This commit fixes it by squeezing the input array whenever the output array is 1-dimensional, which will let the problematic case through. (This shouldn't squeeze inappropriately as dimension compatibility is already checked for conformability before getting to the copy code).
1 parent 7672292 commit 6a81dbb

File tree

3 files changed

+23
-0
lines changed

3 files changed

+23
-0
lines changed

include/pybind11/eigen.h

+1
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
270270
value = Type(fits.rows, fits.cols);
271271
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
272272
if (dims == 1) ref = ref.squeeze();
273+
else if (ref.ndim() == 1) buf = buf.squeeze();
273274

274275
int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr());
275276

tests/test_eigen.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,13 @@ TEST_SUBMODULE(eigen, m) {
288288
m.def("iss738_f1", &adjust_matrix<const Eigen::Ref<const Eigen::MatrixXd> &>, py::arg().noconvert());
289289
m.def("iss738_f2", &adjust_matrix<const Eigen::Ref<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>> &>, py::arg().noconvert());
290290

291+
// test_issue1105
292+
// Issue #1105: when converting from a numpy two-dimensional (Nx1) or (1xN) value into a dense
293+
// eigen Vector or RowVector, the argument would fail to load because the numpy copy would fail:
294+
// numpy won't broadcast a Nx1 into a 1-dimensional vector.
295+
m.def("iss1105_col", [](Eigen::VectorXd) { return true; });
296+
m.def("iss1105_row", [](Eigen::RowVectorXd) { return true; });
297+
291298
// test_named_arguments
292299
// Make sure named arguments are working properly:
293300
m.def("matrix_multiply", [](const py::EigenDRef<const Eigen::MatrixXd> A, const py::EigenDRef<const Eigen::MatrixXd> B)

tests/test_eigen.py

+15
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,21 @@ def test_issue738():
672672
assert np.all(m.iss738_f2(np.array([[1.], [2], [3]])) == np.array([[1.], [12], [23]]))
673673

674674

675+
def test_issue1105():
676+
"""Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen
677+
compile-time row vectors or column vector"""
678+
assert m.iss1105_row(np.ones((1, 7)))
679+
assert m.iss1105_col(np.ones((7, 1)))
680+
681+
# These should still fail (incompatible dimensions):
682+
with pytest.raises(TypeError) as excinfo:
683+
m.iss1105_row(np.ones((7, 1)))
684+
assert "incompatible function arguments" in str(excinfo)
685+
with pytest.raises(TypeError) as excinfo:
686+
m.iss1105_col(np.ones((1, 7)))
687+
assert "incompatible function arguments" in str(excinfo)
688+
689+
675690
def test_custom_operator_new():
676691
"""Using Eigen types as member variables requires a class-specific
677692
operator new with proper alignment"""

0 commit comments

Comments
 (0)