Skip to content

Commit 479e9a5

Browse files
Fix arrays with zero-size dimensions (#4038)
When converting an array to an Eigen matrix, ignore the strides if any dimension size is 0. If the array is empty, the strides aren't relevant, and especially numpy ≥ 1.23 explicitly sets the strides to 0 in this case. (See numpy commit dd5ab7b11520.) Update tests to verify that this works, and continues to work.
1 parent 374a5b0 commit 479e9a5

File tree

3 files changed

+22
-6
lines changed

3 files changed

+22
-6
lines changed

include/pybind11/eigen.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,16 @@ struct EigenConformable {
111111
bool stride_compatible() const {
112112
// To have compatible strides, we need (on both dimensions) one of fully dynamic strides,
113113
// matching strides, or a dimension size of 1 (in which case the stride value is
114-
// irrelevant)
115-
return !negativestrides
116-
&& (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner()
117-
|| (EigenRowMajor ? cols : rows) == 1)
114+
// irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant
115+
// (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly).
116+
if (negativestrides) {
117+
return false;
118+
}
119+
if (rows == 0 || cols == 0) {
120+
return true;
121+
}
122+
return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner()
123+
|| (EigenRowMajor ? cols : rows) == 1)
118124
&& (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer()
119125
|| (EigenRowMajor ? rows : cols) == 1);
120126
}

tests/test_eigen.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,13 @@ TEST_SUBMODULE(eigen, m) {
345345
},
346346
py::arg{}.noconvert());
347347

348-
// test_issue738
349-
// Issue #738: 1xN or Nx1 2D matrices were neither accepted nor properly copied with an
348+
// test_issue738, test_zero_length
349+
// Issue #738: 1×N or N×1 2D matrices were neither accepted nor properly copied with an
350350
// incompatible stride value on the length-1 dimension--but that should be allowed (without
351351
// requiring a copy!) because the stride value can be safely ignored on a size-1 dimension.
352+
// Similarly, 0×N or N×0 matrices were not accepted--again, these should be allowed since
353+
// they contain no data. This particularly affects numpy ≥ 1.23, which sets the strides to
354+
// 0 if any dimension size is 0.
352355
m.def("iss738_f1",
353356
&adjust_matrix<const Eigen::Ref<const Eigen::MatrixXd> &>,
354357
py::arg{}.noconvert());

tests/test_eigen.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,13 @@ def test_issue738():
744744
)
745745

746746

747+
@pytest.mark.parametrize("func", [m.iss738_f1, m.iss738_f2])
748+
@pytest.mark.parametrize("sizes", [(0, 2), (2, 0)])
749+
def test_zero_length(func, sizes):
750+
"""Ignore strides on a length-0 dimension (even if they would be incompatible length > 1)"""
751+
assert np.all(func(np.zeros(sizes)) == np.zeros(sizes))
752+
753+
747754
def test_issue1105():
748755
"""Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen
749756
compile-time row vectors or column vector"""

0 commit comments

Comments
 (0)