From 479e9a50f38cfa8e525a697d7f5d850f851223b5 Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Wed, 29 Jun 2022 14:37:16 -0400 Subject: [PATCH] Fix arrays with zero-size dimensions (#4038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- include/pybind11/eigen.h | 14 ++++++++++---- tests/test_eigen.cpp | 7 +++++-- tests/test_eigen.py | 7 +++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/include/pybind11/eigen.h b/include/pybind11/eigen.h index f658168de..71df3eb8b 100644 --- a/include/pybind11/eigen.h +++ b/include/pybind11/eigen.h @@ -111,10 +111,16 @@ struct EigenConformable { bool stride_compatible() const { // To have compatible strides, we need (on both dimensions) one of fully dynamic strides, // matching strides, or a dimension size of 1 (in which case the stride value is - // irrelevant) - return !negativestrides - && (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner() - || (EigenRowMajor ? cols : rows) == 1) + // irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant + // (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly). + if (negativestrides) { + return false; + } + if (rows == 0 || cols == 0) { + return true; + } + return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner() + || (EigenRowMajor ? cols : rows) == 1) && (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer() || (EigenRowMajor ? rows : cols) == 1); } diff --git a/tests/test_eigen.cpp b/tests/test_eigen.cpp index 74e76eb7e..591dacc62 100644 --- a/tests/test_eigen.cpp +++ b/tests/test_eigen.cpp @@ -345,10 +345,13 @@ TEST_SUBMODULE(eigen, m) { }, py::arg{}.noconvert()); - // test_issue738 - // Issue #738: 1xN or Nx1 2D matrices were neither accepted nor properly copied with an + // test_issue738, test_zero_length + // Issue #738: 1×N or N×1 2D matrices were neither accepted nor properly copied with an // incompatible stride value on the length-1 dimension--but that should be allowed (without // requiring a copy!) because the stride value can be safely ignored on a size-1 dimension. + // Similarly, 0×N or N×0 matrices were not accepted--again, these should be allowed since + // they contain no data. This particularly affects numpy ≥ 1.23, which sets the strides to + // 0 if any dimension size is 0. m.def("iss738_f1", &adjust_matrix &>, py::arg{}.noconvert()); diff --git a/tests/test_eigen.py b/tests/test_eigen.py index 093773a51..a1c114aed 100644 --- a/tests/test_eigen.py +++ b/tests/test_eigen.py @@ -744,6 +744,13 @@ def test_issue738(): ) +@pytest.mark.parametrize("func", [m.iss738_f1, m.iss738_f2]) +@pytest.mark.parametrize("sizes", [(0, 2), (2, 0)]) +def test_zero_length(func, sizes): + """Ignore strides on a length-0 dimension (even if they would be incompatible length > 1)""" + assert np.all(func(np.zeros(sizes)) == np.zeros(sizes)) + + def test_issue1105(): """Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen compile-time row vectors or column vector"""