From 37b59f03b89bc19e102af59c0bb8b32976b52a9c Mon Sep 17 00:00:00 2001 From: Francois Bleibel <67498661+fbleibel-g@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:55:10 +0000 Subject: [PATCH 1/2] Add .vscode to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 24679af..aa40fa5 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ docs/* # Visual Studio folder status .vs +.vscode # perf files perf.data From bf18c3db8880925faa055983943043d33e73b7fe Mon Sep 17 00:00:00 2001 From: Francois Bleibel <67498661+fbleibel-g@users.noreply.github.com> Date: Tue, 28 May 2024 01:38:34 +0000 Subject: [PATCH 2/2] Add shape::, array_ref::, and array::bounds() Helps avoid the common mistake of creating an array from an existing argument's shape, inheriting the strides and possibly violating compile-time constraints. (e.g. creating a planar image from an interleaved image's shape). --- include/array/array.h | 31 +++++++++++++++++++++++++++++++ test/array.cpp | 21 +++++++++++++++++++++ test/shape.cpp | 25 +++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/include/array/array.h b/include/array/array.h index 187670b..a431ed4 100644 --- a/include/array/array.h +++ b/include/array/array.h @@ -1030,6 +1030,12 @@ NDARRAY_HOST_DEVICE const DimsSrc& assert_dims_compatible(const DimsSrc& src) { return src; } +/** Return a tuple of generic `dims` with same min and extents and all strides set to `unresolved`. */ +template +auto bounds_tuple(const Dims& dims, index_sequence) { + return std::make_tuple(dim<>(std::get(dims).min(), std::get(dims).extent(), unresolved)...); +} + } // namespace internal template @@ -1293,6 +1299,13 @@ class shape { NDARRAY_HOST_DEVICE index_t rows() const { return i().extent(); } NDARRAY_HOST_DEVICE index_t columns() const { return j().extent(); } + /** Returns a shape with dynamic dims, with the same min and extents but + * strides initialized to `nda::unresolved`. This can be used to create + * an array with the same dimensions but different compile-time constraints. */ + NDARRAY_HOST_DEVICE auto bounds() const { + return make_shape_from_tuple(internal::bounds_tuple(dims_, dim_indices())); + } + /** A shape is equal to another shape if the dim objects of each * dimension from both shapes are equal. */ template > @@ -2115,6 +2128,15 @@ class array_ref { } const nda::dim<> dim(size_t d) const { return shape_.dim(d); } NDARRAY_HOST_DEVICE size_type size() const { return shape_.size(); } + /** Returns a shape with dynamic dims, with the same min and extents but + * strides initialized to `nda::unresolved`. This can be used to create + * an array with the same dimensions but different compile-time constraints: + * + * nda::array_ref ref(...); + * nda::array y(ref.bounds()); // Compact array with the same `min` + * // and `extents` as `ref`. + */ + NDARRAY_HOST_DEVICE auto bounds() const { return shape_.bounds(); } NDARRAY_HOST_DEVICE bool empty() const { return base() != nullptr ? shape_.empty() : true; } NDARRAY_HOST_DEVICE bool is_compact() const { return shape_.is_compact(); } @@ -2577,6 +2599,15 @@ class array { } const nda::dim<> dim(size_t d) const { return shape_.dim(d); } size_type size() const { return shape_.size(); } + /** Returns a shape with dynamic dims, with the same min and extents but + * strides initialized to `nda::unresolved`. This can be used to create + * an array with the same dimensions but different compile-time constraints: + * + * nda::array other(...); + * nda::array y(other.bounds()); // Compact array with the same `min` + * // and `extents` as `other`. + */ + auto bounds() const { return shape_.bounds(); } bool empty() const { return shape_.empty(); } bool is_compact() const { return shape_.is_compact(); } diff --git a/test/array.cpp b/test/array.cpp index 92fbd05..973d1f8 100644 --- a/test/array.cpp +++ b/test/array.cpp @@ -59,6 +59,27 @@ TEST(array_default_constructor) { sparse.clear(); } +TEST(array_construct_from_bounds) { + // Illustrate creating an array from incompatible compile-time + // shapes using bounds(). + using SrcShape = shape, dim<>>; + using DstShape = shape, dense_dim<>>; // dense_dim is swapped. + + SrcShape src_shape({-1, 10}, {2, 5, /*stride =*/100}); + auto src = array(src_shape); + auto dst = array(src.bounds()); + + // min and extent are preserved. + ASSERT_EQ(src.dim<0>().min(), dst.dim<0>().min()); + ASSERT_EQ(src.dim<0>().extent(), dst.dim<0>().extent()); + ASSERT_EQ(src.dim<1>().min(), dst.dim<1>().min()); + ASSERT_EQ(src.dim<1>().extent(), dst.dim<1>().extent()); + // Ensure that `dst` did not inherit the strides of its parent and is + // created compact. + ASSERT(!src.is_compact()); + ASSERT(dst.is_compact()); +} + TEST(array_static_convertibility) { using A0 = array_of_rank; using A3 = array_of_rank; diff --git a/test/shape.cpp b/test/shape.cpp index 1fafb2f..681bc93 100644 --- a/test/shape.cpp +++ b/test/shape.cpp @@ -26,6 +26,7 @@ TEST(shape_scalar) { ASSERT_EQ(s.flat_extent(), 1); ASSERT_EQ(s.size(), 1); ASSERT_EQ(s(), 0); + ASSERT_EQ(s.bounds().rank(), 0); } TEST(shape_1d) { @@ -205,6 +206,30 @@ TEST(auto_strides) { test_auto_strides<10>(); } +TEST(bounds) { + dim x; + dense_dim<> y(-2, 12); + auto s = make_shape(x, y); + s.resolve(); + + auto bounds = s.bounds(); + // Returns a generic shape w/o compile-time min, extents, or strides. + ASSERT_EQ(bounds.dim<0>().Min, dynamic); + ASSERT_EQ(bounds.dim<0>().Extent, dynamic); + ASSERT_EQ(bounds.dim<0>().Stride, dynamic); + ASSERT_EQ(bounds.dim<1>().Min, dynamic); + ASSERT_EQ(bounds.dim<1>().Extent, dynamic); + ASSERT_EQ(bounds.dim<1>().Stride, dynamic); + // Check that dynamic min and extents are preserved. + ASSERT_EQ(bounds.dim<0>().min(), x.min()); + ASSERT_EQ(bounds.dim<0>().extent(), x.extent()); + ASSERT_EQ(bounds.dim<1>().min(), y.min()); + ASSERT_EQ(bounds.dim<1>().extent(), y.extent()); + // Bounds have strides set to unresolved. + ASSERT_EQ(bounds.dim<0>().stride(), nda::unresolved); + ASSERT_EQ(bounds.dim<1>().stride(), nda::unresolved); +} + TEST(broadcast_dim) { dim<> x(0, 10, 1); broadcast_dim<> y;