kfr

Fast, modern C++ DSP framework, FFT, Sample Rate Conversion, FIR/IIR/Biquad Filters (SSE, AVX, AVX-512, ARM NEON)
Log | Files | Refs | README

commit f880d18974b5694d5cd865925475f753b29292e9
parent 398dc194b4299503dd2224df67fd2566b2379d41
Author: [email protected] <[email protected]>
Date:   Mon, 19 Sep 2022 14:49:04 +0100

Init tensor from initializer_list, slicing

Diffstat:
Minclude/kfr/base/expression.hpp | 6+++---
Minclude/kfr/base/impl/static_array.hpp | 2+-
Minclude/kfr/base/new_expressions.hpp | 32+++++++++++++++++---------------
Minclude/kfr/base/shape.hpp | 136++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Minclude/kfr/base/tensor.hpp | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mtests/unit/base/tensor.cpp | 222+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
6 files changed, 527 insertions(+), 100 deletions(-)

diff --git a/include/kfr/base/expression.hpp b/include/kfr/base/expression.hpp @@ -249,8 +249,8 @@ KFR_INTRINSIC index_t axis_stop(const shape<outdims>& sh) template <size_t width = 0, index_t Axis = infinite_size, typename Out, typename In, size_t gw = 1, typename Tin = expression_value_type<In>, typename Tout = expression_value_type<Out>, index_t outdims = expression_dims<Out>, CMT_ENABLE_IF(expression_dims<Out> > 0)> -static auto tprocess(Out&& out, In&& in, shape<outdims> start = 0, shape<outdims> size = infinite_size, - csize_t<gw> = {}) -> shape<outdims> +static auto tprocess(Out&& out, In&& in, shape<outdims> start = shape<outdims>(0), + shape<outdims> size = shape<outdims>(infinite_size), csize_t<gw> = {}) -> shape<outdims> { using internal::axis_start; using internal::axis_stop; @@ -274,7 +274,7 @@ static auto tprocess(Out&& out, In&& in, shape<outdims> start = 0, shape<outdims const shape<outdims> outshape = shapeof(out); const shape<indims> inshape = shapeof(in); if (CMT_UNLIKELY(!internal_generic::can_assign_from(outshape, inshape))) - return { 0 }; + return shape<outdims>{ 0 }; shape<outdims> stop = min(start.add_inf(size), outshape); index_t in_size = 0; diff --git a/include/kfr/base/impl/static_array.hpp b/include/kfr/base/impl/static_array.hpp @@ -67,7 +67,7 @@ struct static_array_base<T, csizes_t<indices...>> constexpr static_array_base& operator=(static_array_base&&) = default; template <int dummy = 0, CMT_ENABLE_IF(dummy == 0 && static_size > 1)> - KFR_MEM_INTRINSIC constexpr static_array_base(value_type value) + KFR_MEM_INTRINSIC constexpr explicit static_array_base(value_type value) { (static_cast<void>(array[indices] = value), ...); } diff --git a/include/kfr/base/new_expressions.hpp b/include/kfr/base/new_expressions.hpp @@ -239,7 +239,7 @@ template <typename Arg, index_t Axis, size_t N, typename Traits = expression_tra KFR_INTRINSIC vec<T, N> get_elements(const xreverse<Arg>& self, const shape<Traits::dims>& index, const axis_params<Axis, N>& sh) { - return reverse(get_elements(self.first(), self.input_shape.sub(index).sub(N), sh)); + return reverse(get_elements(self.first(), self.input_shape.sub(index).sub(shape<Traits::dims>(N)), sh)); } } // namespace CMT_ARCH_NAME @@ -340,7 +340,7 @@ struct expression_traits<xreshape<Arg, OutDims>> : expression_traits_defaults { return self.out_shape; } - KFR_MEM_INTRINSIC constexpr static shape<dims> shapeof() { return { 0 }; } + KFR_MEM_INTRINSIC constexpr static shape<dims> shapeof() { return shape<dims>{ 0 }; } }; inline namespace CMT_ARCH_NAME @@ -374,16 +374,19 @@ KFR_INTRINSIC vec<T, N> get_elements(const xreshape<Arg, outdims>& self, const s vec<T, N> result; bool done = false; - cforeach(cvalseq_t<index_t, indims, 0>{}, - [&](auto n) CMT_INLINE_LAMBDA - { - constexpr index_t axis = val_of<decltype(n)>({}); - if (!done && diff_idx[axis] == N - 1) + if (diff_idx.sum() == N - 1) + { + cforeach(cvalseq_t<index_t, indims, 0>{}, + [&](auto n) CMT_INLINE_LAMBDA { - result = get_elements(self.first(), first_idx, axis_params<axis, N>{}); - done = true; - } - }); + constexpr index_t axis = val_of<decltype(n)>({}); + if (!done && diff_idx[axis] == N - 1) + { + result = get_elements(self.first(), first_idx, axis_params<axis, N>{}); + done = true; + } + }); + } if (!done) { @@ -391,10 +394,9 @@ KFR_INTRINSIC vec<T, N> get_elements(const xreshape<Arg, outdims>& self, const s CMT_LOOP_NOUNROLL for (size_t i = 0; i < N; ++i) { - tmp[i] = get_elements( - self.first(), - self.in_shape.from_flat(self.out_shape.to_flat(index.add_at(i, cindex<Axis>))), - axis_params<indims - 1, 1>{}) + shape<Traits::dims> idx = index.add_at(i, cindex<Axis>); + tmp[i] = get_elements(self.first(), self.in_shape.from_flat(self.out_shape.to_flat(idx)), + axis_params<indims - 1, 1>{}) .front(); } result = tmp; diff --git a/include/kfr/base/shape.hpp b/include/kfr/base/shape.hpp @@ -33,6 +33,7 @@ #include "../simd/vec.hpp" #include <bitset> +#include <optional> namespace kfr { @@ -48,7 +49,8 @@ using signed_index_t = int32_t; using index_t = uint32_t; using signed_index_t = int32_t; #endif -constexpr inline index_t max_index_t = std::numeric_limits<index_t>::max(); +constexpr inline index_t max_index_t = std::numeric_limits<index_t>::max(); +constexpr inline signed_index_t max_sindex_t = std::numeric_limits<signed_index_t>::max(); template <index_t val> using cindex_t = cval_t<index_t, val>; @@ -88,7 +90,7 @@ namespace internal_generic template <index_t dims> KFR_INTRINSIC bool increment_indices(shape<dims>& indices, const shape<dims>& start, const shape<dims>& stop, index_t dim = dims - 1); -} +} // namespace internal_generic template <index_t dims> struct shape : static_array_base<index_t, csizeseq_t<dims>> @@ -109,6 +111,16 @@ struct shape : static_array_base<index_t, csizeseq_t<dims>> } } + index_t trailing_zeros() const + { + for (index_t i = 0; i < dims; ++i) + { + if (revindex(i) != 0) + return i; + } + return dims; + } + bool le(const shape& other) const { if constexpr (dims == 1) @@ -136,6 +148,7 @@ struct shape : static_array_base<index_t, csizeseq_t<dims>> } shape add(const shape& other) const { return **this + *other; } shape sub(const shape& other) const { return **this - *other; } + index_t sum() const { return hsum(**this); } shape add_inf(const shape& other) const { @@ -358,29 +371,35 @@ struct cursor shape<Dims> maximum; }; +using opt_index_t = std::optional<signed_index_t>; + struct tensor_range { - index_t start = 0; - index_t stop = max_index_t; - index_t step = 1; - - constexpr KFR_INTRINSIC index_t size() const { return stop - start; } + opt_index_t start; + opt_index_t stop; + opt_index_t step; }; -constexpr KFR_INTRINSIC tensor_range tstart(index_t start, index_t step = 1) +constexpr KFR_INTRINSIC tensor_range trange(std::optional<signed_index_t> start = std::nullopt, + std::optional<signed_index_t> stop = std::nullopt, + std::optional<signed_index_t> step = std::nullopt) { - return { start, max_index_t, step }; + return { start, stop, step }; } -constexpr KFR_INTRINSIC tensor_range tstop(index_t stop, index_t step = 1) { return { 0, stop, step }; } -constexpr KFR_INTRINSIC tensor_range trange(index_t start, index_t stop, index_t step = 1) + +constexpr KFR_INTRINSIC tensor_range tall() { return trange(); } +constexpr KFR_INTRINSIC tensor_range tstart(signed_index_t start, signed_index_t step = 1) { - return { start, stop, step }; + return trange(start, std::nullopt, step); +} +constexpr KFR_INTRINSIC tensor_range tstop(signed_index_t stop, signed_index_t step = 1) +{ + return trange(std::nullopt, stop, step); } -constexpr KFR_INTRINSIC tensor_range trange_n(index_t start, index_t size, index_t step = 1) +constexpr KFR_INTRINSIC tensor_range tstep(signed_index_t step = 1) { - return { start, start + size, step }; + return trange(std::nullopt, std::nullopt, step); } -constexpr KFR_INTRINSIC tensor_range tall() { return { 0, max_index_t, 1 }; } namespace internal_generic { @@ -400,32 +419,6 @@ constexpr KFR_INTRINSIC shape<dims> strides_for_shape(const shape<dims>& sh, ind return strides; } -template <typename Index> -constexpr KFR_INTRINSIC index_t get_start(const Index& idx) -{ - if constexpr (std::is_same_v<std::decay_t<Index>, tensor_range>) - { - return idx.start; - } - else - { - static_assert(std::is_convertible_v<Index, index_t>); - return static_cast<index_t>(idx); - } -} -template <typename Index> -constexpr KFR_INTRINSIC index_t get_stop(const Index& idx) -{ - if constexpr (std::is_same_v<std::decay_t<Index>, tensor_range>) - { - return idx.stop; - } - else - { - static_assert(std::is_convertible_v<Index, index_t>); - return static_cast<index_t>(idx) + 1; - } -} template <size_t dims, size_t outdims, bool... ranges> constexpr KFR_INTRINSIC shape<outdims> compact_shape(const shape<dims>& in) { @@ -499,7 +492,7 @@ constexpr shape<outdims> common_shape(const shape<dims1>& shape1, const shape<di else { // broadcast failed - result = 0; + result = shape<outdims>(0); return result; } } @@ -619,7 +612,7 @@ KFR_INTRINSIC shape<dims> increment_indices_return(const shape<dims>& indices, c } else { - return null_index; + return shape<dims>(null_index); } } @@ -628,6 +621,63 @@ constexpr KFR_INTRINSIC size_t count_dimensions() { return ((std::is_same_v<std::decay_t<Index>, tensor_range> ? 1 : 0) + ...); } + +template <typename U> +struct type_of_list +{ + using value_type = U; +}; + +template <typename U> +struct type_of_list<std::initializer_list<U>> +{ + using value_type = typename type_of_list<U>::value_type; +}; + +template <typename U> +using type_of_list_t = typename type_of_list<U>::value_type; + +template <typename U> +constexpr KFR_INTRINSIC shape<1> shape_of_list(const std::initializer_list<U>& list) +{ + return list.size(); +} + +template <typename U> +constexpr KFR_INTRINSIC auto shape_of_list(const std::initializer_list<std::initializer_list<U>>& list) +{ + return shape_of_list(*list.begin()); +} + +template <typename U> +constexpr KFR_INTRINSIC U list_get(const std::initializer_list<U>& list, const shape<1>& idx) +{ + return list[idx.front()]; +} + +template <typename U, index_t dims> +constexpr KFR_INTRINSIC auto list_get(const std::initializer_list<std::initializer_list<U>>& list, + const shape<dims>& idx) +{ + return list_get(list[idx[0]], idx.template trim<dims - 1>()); +} + +template <typename U, typename T> +KFR_FUNCTION T* list_copy_recursively(const std::initializer_list<U>& list, T* dest) +{ + for (const auto& value : list) + *dest++ = static_cast<T>(value); + return dest; +} + +template <typename U, typename T> +KFR_FUNCTION T* list_copy_recursively(const std::initializer_list<std::initializer_list<U>>& list, T* dest) +{ + for (const auto& sublist : list) + dest = list_copy_recursively(sublist, dest); + return dest; +} + } // namespace internal_generic template <index_t dims> diff --git a/include/kfr/base/tensor.hpp b/include/kfr/base/tensor.hpp @@ -118,9 +118,9 @@ public: // prefix KFR_MEM_INTRINSIC tensor_iterator& operator++() { - if (!internal_generic::increment_indices(indices, kfr::shape<dims>(0), src->m_shape)) + if (!internal_generic::increment_indices(indices, shape_type(0), src->m_shape)) { - indices = internal_generic::null_index; + indices = shape_type(internal_generic::null_index); } return *this; } @@ -188,18 +188,45 @@ public: } KFR_INTRINSIC tensor(const shape_type& shape, T value) : tensor(shape) { - std::fill(contiguous_begin(), contiguous_end(), value); + std::fill(contiguous_begin_unsafe(), contiguous_end_unsafe(), value); } KFR_INTRINSIC tensor(const shape_type& shape, const shape_type& strides, T value) : tensor(shape, strides) { std::fill(begin(), end(), value); } - KFR_INTRINSIC tensor(const shape_type& shape, std::initializer_list<T> values) : tensor(shape) + KFR_INTRINSIC tensor(const shape_type& shape, const std::initializer_list<T>& values) : tensor(shape) { if (values.size() != m_size) throw std::runtime_error("Invalid initializer provided for kfr::tensor"); - std::copy(values.begin(), values.end(), contiguous_begin()); + std::copy(values.begin(), values.end(), contiguous_begin_unsafe()); + } + + template <typename U, KFR_ENABLE_IF(std::is_convertible_v<U, T>&& dims == 1)> + KFR_INTRINSIC tensor(const std::initializer_list<U>& values) : tensor(shape_type{ values.size() }) + { + internal_generic::list_copy_recursively(values, contiguous_begin_unsafe()); + } + template <typename U, KFR_ENABLE_IF(std::is_convertible_v<U, T>&& dims == 2)> + KFR_INTRINSIC tensor(const std::initializer_list<std::initializer_list<U>>& values) + : tensor(shape_type{ values.size(), values.begin()->size() }) + { + internal_generic::list_copy_recursively(values, contiguous_begin_unsafe()); + } + template <typename U, KFR_ENABLE_IF(std::is_convertible_v<U, T>&& dims == 3)> + KFR_INTRINSIC tensor(const std::initializer_list<std::initializer_list<std::initializer_list<U>>>& values) + : tensor(shape_type{ values.size(), values.begin()->size(), values.begin()->begin()->size() }) + { + internal_generic::list_copy_recursively(values, contiguous_begin_unsafe()); + } + template <typename U, KFR_ENABLE_IF(std::is_convertible_v<U, T>&& dims == 4)> + KFR_INTRINSIC tensor( + const std::initializer_list<std::initializer_list<std::initializer_list<std::initializer_list<U>>>>& + values) + : tensor(shape_type{ values.size(), values.begin()->size(), values.begin()->begin()->size(), + values.begin()->begin()->begin()->size() }) + { + internal_generic::list_copy_recursively(values, contiguous_begin_unsafe()); } KFR_INTRINSIC tensor(const shape_type& shape, const shape_type& strides, std::initializer_list<T> values) @@ -214,10 +241,18 @@ public: KFR_INTRINSIC size_type size() const { return m_size; } - KFR_INTRINSIC tensor_iterator begin() const { return tensor_iterator{ this, 0 }; } + KFR_INTRINSIC bool empty() const { return m_size == 0; } + + KFR_INTRINSIC tensor_iterator begin() const + { + if (empty()) + return tensor_iterator{ this, shape_type(internal_generic::null_index) }; + else + return tensor_iterator{ this, shape_type(0) }; + } KFR_INTRINSIC tensor_iterator end() const { - return tensor_iterator{ this, internal_generic::null_index }; + return tensor_iterator{ this, shape_type(internal_generic::null_index) }; } KFR_INTRINSIC void require_contiguous() const @@ -310,22 +345,97 @@ public: template <typename... Index> static constexpr bool has_tensor_range = (std::is_same_v<Index, tensor_range> || ...); + KFR_MEM_INTRINSIC static void get_range(index_t& start, index_t& shape, index_t& step, + signed_index_t tsize, index_t iidx) + { + signed_index_t tstart = iidx; + tstart = tstart < 0 ? tsize + tstart : tstart; + start = tstart; + shape = tstart < tsize ? 1 : 0; + step = 1; + } + + KFR_MEM_INTRINSIC static void get_range(index_t& start, index_t& shape, index_t& step, + signed_index_t tsize, const tensor_range& iidx) + { + signed_index_t tstep = iidx.step.value_or(1); + signed_index_t tstart; + signed_index_t tstop; + if (tstep >= 0) + { + tstart = iidx.start.value_or(0); + tstop = iidx.stop.value_or(tsize); + } + else + { + tstart = iidx.start ? *iidx.start + 1 : tsize; + tstop = iidx.stop ? *iidx.stop + 1 : 0; + } + tstart = tstart < 0 ? tsize + tstart : tstart; + tstart = std::max(std::min(tstart, tsize), signed_index_t(0)); + if (tstep == 0) + { + start = tstart; + shape = tstop - tstart; + step = 0; + } + else + { + tstop = tstop < 0 ? tsize + tstop : tstop; + tstop = std::max(std::min(tstop, tsize), signed_index_t(0)); + if (tstep >= 0) + { + tstop = std::max(tstop, tstart); + start = tstart; + shape = (tstop - tstart + tstep - 1) / tstep; + step = tstep; + } + else + { + tstart = std::max(tstart, tstop); + shape = (tstart - tstop + -tstep - 1) / -tstep; + start = tstart - 1; + step = tstep; + } + } + } + + template <index_t... Num, typename... Index> + KFR_MEM_INTRINSIC void get_ranges(shape_type& start, shape_type& shape, shape_type& step, + cvals_t<index_t, Num...> indices, const std::tuple<Index...>& idx) const + { + cforeach(indices, + [&](auto i_) CMT_INLINE_LAMBDA + { + constexpr index_t i = val_of(decltype(i_)()); + signed_index_t tsize = static_cast<signed_index_t>(m_shape[i]); + if constexpr (i < sizeof...(Index)) + { + get_range(start[i], shape[i], step[i], tsize, std::get<i>(idx)); + } + else + { + start[i] = 0; + shape[i] = tsize; + step[i] = 1; + } + }); + } + template <typename... Index, size_t ndimOut = internal_generic::count_dimensions<Index...>() + (dims - sizeof...(Index)), std::enable_if_t<has_tensor_range<Index...> || (sizeof...(Index) < dims)>* = nullptr> KFR_MEM_INTRINSIC tensor<T, ndimOut> operator()(const Index&... idx) const { - shape_type start{ internal_generic::get_start(idx)... }; - shape_type stop{ internal_generic::get_stop(idx)... }; - stop = min(*stop, *m_shape); - shape_type strides = m_strides; - for (index_t i = sizeof...(Index); i < dims; ++i) - { - start[i] = 0; - stop[i] = m_shape[i]; - } - T* data = m_data + calc_index(start); - shape_type shape = *stop - *start; + shape_type start; + shape_type shape; + shape_type step; + get_ranges(start, shape, step, cvalseq<index_t, dims>, std::make_tuple(idx...)); + shape_type strides = *step * *m_strides; + // shape_type absstep = abs(*step); + + T* data = m_data + calc_index(start); + // shape_type shape = ((*stop - *start) + (*absstep - 1)) / *absstep; return tensor<T, ndimOut>{ data, @@ -382,7 +492,7 @@ public: KFR_MEM_INTRINSIC tensor<T, 1> flatten_may_copy(bool allow_copy = false) const { - return reshape(shape<1>{ m_size }, allow_copy); + return reshape_may_copy(kfr::shape<1>{ m_size }, allow_copy); } KFR_MEM_INTRINSIC tensor copy() const @@ -686,6 +796,65 @@ public: KFR_MEM_INTRINSIC bool is_last_contiguous() const { return m_strides.back() == 1; } + std::string to_string(std::string format = "%g", int max_columns = 16, int max_dimensions = INT_MAX, + std::string separator = ", ", std::string open = "{", std::string close = "}") const + { + if (max_columns == 0) + max_columns = INT_MAX; + std::stringstream ss; + for (index_t i = 0; i < dims; ++i) + ss << open; + + if (!empty()) + { + shape_type index{ 0 }; + std::string open_filler(open.size(), ' '); + std::string separator_trimmed = separator.substr(0, 1 + separator.find_last_not_of(" \t")); + char buf[64]; + int columns = 0; + do + { + int c = sprintf_s(buf, std::size(buf), format.c_str(), access(index)); + index_t z = index.trailing_zeros(); + if ((z > 0 && columns > 0) || columns >= max_columns) + { + for (index_t i = 0; i < z; ++i) + ss << close; + + if (z > max_dimensions || columns >= max_columns) + { + if (columns > 0) + ss << separator_trimmed; + ss << std::endl; + for (index_t i = 0; i < dims - z; ++i) + ss << open_filler; + } + else + { + if (columns > 0) + ss << separator; + } + for (index_t i = 0; i < z; ++i) + ss << open; + + columns = 0; + } + else + { + if (columns > 0) + ss << separator; + } + if (c < 1) + return ""; + ss.write(buf, c); + ++columns; + } while (internal_generic::increment_indices<dims>(index, shape_type{ 0 }, m_shape)); + } + for (index_t i = 0; i < dims; ++i) + ss << close; + return ss.str(); + } + private: template <typename Input> KFR_MEM_INTRINSIC void assign_expr(Input&& input) const @@ -734,7 +903,7 @@ struct expression_traits<tensor<T, Dims>> : expression_traits_defaults { return self.shape(); } - KFR_MEM_INTRINSIC constexpr static shape<dims> shapeof() { return { 0 }; } + KFR_MEM_INTRINSIC constexpr static shape<dims> shapeof() { return shape<dims>{ 0 }; } }; inline namespace CMT_ARCH_NAME @@ -779,13 +948,21 @@ tensor<T, outdims> tapply(const tensor<T, dims1>& x, const tensor<T, dims2>& y, return result; } +template <size_t width = 0, index_t Axis = infinite_size, typename E, typename Traits = expression_traits<E>> +tensor<typename Traits::value_type, Traits::dims> trender(const E& expr) +{ + tensor<typename Traits::value_type, Traits::dims> result(Traits::shapeof(expr)); + tprocess<width, Axis>(result, expr); + return result; +} + } // namespace CMT_ARCH_NAME } // namespace kfr namespace cometa { -template <size_t dims> +template <kfr::index_t dims> struct representation<kfr::shape<dims>> { using type = std::string; @@ -798,7 +975,7 @@ struct representation<kfr::shape<dims>> else { std::string s; - for (size_t i = 0; i < dims; ++i) + for (kfr::index_t i = 0; i < dims; ++i) { if (CMT_LIKELY(i > 0)) s += ", "; @@ -808,6 +985,14 @@ struct representation<kfr::shape<dims>> } } }; + +template <typename T, kfr::index_t dims> +struct representation<kfr::tensor<T, dims>> +{ + using type = std::string; + static std::string get(const kfr::tensor<T, dims>& value) { return value.to_string(); } +}; + } // namespace cometa CMT_PRAGMA_MSVC(warning(pop)) diff --git a/tests/unit/base/tensor.cpp b/tests/unit/base/tensor.cpp @@ -14,6 +14,7 @@ CMT_PRAGMA_MSVC(warning(disable : 5051)) namespace kfr { + inline namespace CMT_ARCH_NAME { @@ -257,8 +258,8 @@ struct expression_traits<tcounter<T, Dims>> : expression_traits_defaults using value_type = T; constexpr static size_t dims = Dims; - constexpr static shape<dims> shapeof(const tcounter<T, Dims>& self) { return max_index_t; } - constexpr static shape<dims> shapeof() { return max_index_t; } + constexpr static shape<dims> shapeof(const tcounter<T, Dims>& self) { return shape<dims>(max_index_t); } + constexpr static shape<dims> shapeof() { return shape<dims>(max_index_t); } }; template <typename T, size_t N1> @@ -267,8 +268,8 @@ struct expression_traits<std::array<T, N1>> : expression_traits_defaults using value_type = T; constexpr static size_t dims = 1; - constexpr static shape<1> shapeof(const std::array<T, N1>& self) { return { N1 }; } - constexpr static shape<1> shapeof() { return { N1 }; } + constexpr static shape<1> shapeof(const std::array<T, N1>& self) { return shape<1>{ N1 }; } + constexpr static shape<1> shapeof() { return shape<1>{ N1 }; } }; template <typename T, size_t N1, size_t N2> @@ -277,8 +278,11 @@ struct expression_traits<std::array<std::array<T, N1>, N2>> : expression_traits_ using value_type = T; constexpr static size_t dims = 2; - constexpr static shape<2> shapeof(const std::array<std::array<T, N1>, N2>& self) { return { N2, N1 }; } - constexpr static shape<2> shapeof() { return { N2, N1 }; } + constexpr static shape<2> shapeof(const std::array<std::array<T, N1>, N2>& self) + { + return shape<2>{ N2, N1 }; + } + constexpr static shape<2> shapeof() { return shape<2>{ N2, N1 }; } }; inline namespace CMT_ARCH_NAME @@ -459,11 +463,155 @@ TEST(xfunction_test) { { 501.f, 502.f, 503.f, 504.f, 505.f } } } }); } +template <typename Type, index_t Dims> +KFR_FUNCTION tcounter<Type, Dims> debug_counter(uint64_t scale = 10) +{ + tcounter<Type, Dims> result; + result.start = 0; + uint64_t val = 1; + for (size_t i = 0; i < Dims; i++) + { + result.steps[Dims - 1 - i] = val; + val *= scale; + } + return result; +} + +static std::string nl = R"( +)"; + +TEST(tensor_tostring) +{ + tensor<float, 1> t1(shape{ 60 }); + t1 = debug_counter<float, 1>(); + CHECK(nl + t1.to_string("%2.0f", 12, 0) + nl == R"( +{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59} +)"); + + tensor<float, 2> t2(shape{ 12, 5 }); + t2 = debug_counter<float, 2>(); + CHECK(nl + t2.to_string("%3.0f", 16, 0) + nl == R"( +{{ 0, 1, 2, 3, 4}, + { 10, 11, 12, 13, 14}, + { 20, 21, 22, 23, 24}, + { 30, 31, 32, 33, 34}, + { 40, 41, 42, 43, 44}, + { 50, 51, 52, 53, 54}, + { 60, 61, 62, 63, 64}, + { 70, 71, 72, 73, 74}, + { 80, 81, 82, 83, 84}, + { 90, 91, 92, 93, 94}, + {100, 101, 102, 103, 104}, + {110, 111, 112, 113, 114}} +)"); + + tensor<float, 3> t3(shape{ 3, 4, 5 }); + t3 = debug_counter<float, 3>(); + CHECK(nl + t3.to_string("%4.0f", 16, 0) + nl == R"( +{{{ 0, 1, 2, 3, 4}, + { 10, 11, 12, 13, 14}, + { 20, 21, 22, 23, 24}, + { 30, 31, 32, 33, 34}}, + {{ 100, 101, 102, 103, 104}, + { 110, 111, 112, 113, 114}, + { 120, 121, 122, 123, 124}, + { 130, 131, 132, 133, 134}}, + {{ 200, 201, 202, 203, 204}, + { 210, 211, 212, 213, 214}, + { 220, 221, 222, 223, 224}, + { 230, 231, 232, 233, 234}}} +)"); + + tensor<float, 4> t4(shape{ 3, 2, 2, 5 }); + t4 = debug_counter<float, 4>(); + CHECK(nl + t4.to_string("%5.0f", 16, 0) + nl == R"( +{{{{ 0, 1, 2, 3, 4}, + { 10, 11, 12, 13, 14}}, + {{ 100, 101, 102, 103, 104}, + { 110, 111, 112, 113, 114}}}, + {{{ 1000, 1001, 1002, 1003, 1004}, + { 1010, 1011, 1012, 1013, 1014}}, + {{ 1100, 1101, 1102, 1103, 1104}, + { 1110, 1111, 1112, 1113, 1114}}}, + {{{ 2000, 2001, 2002, 2003, 2004}, + { 2010, 2011, 2012, 2013, 2014}}, + {{ 2100, 2101, 2102, 2103, 2104}, + { 2110, 2111, 2112, 2113, 2114}}}} +)"); + + tensor<float, 2> t5(shape{ 10, 1 }); + t5 = debug_counter<float, 2>(); + CHECK(nl + t5.to_string("%.0f", 12, 1) + nl == R"( +{{0}, {10}, {20}, {30}, {40}, {50}, {60}, {70}, {80}, {90}} +)"); +} + +template <typename T, index_t dims1, index_t dims2> +static void test_reshape_body(const tensor<T, dims1>& t1, const tensor<T, dims2>& t2) +{ + CHECK(t1.reshape_may_copy(t2.shape(), true) == t2); + + cforeach(csizeseq<dims2>, + [&](auto x) + { + constexpr index_t axis = val_of(decltype(x)()); + ::testo::scope s( + as_string("axis = ", axis, " shape = (", t1.shape(), ") -> (", t2.shape(), ")")); + CHECK(trender<1, axis>(x_reshape(t1, t2.shape())) == t2); + CHECK(trender<2, axis>(x_reshape(t1, t2.shape())) == t2); + CHECK(trender<4, axis>(x_reshape(t1, t2.shape())) == t2); + CHECK(trender<8, axis>(x_reshape(t1, t2.shape())) == t2); + }); +} + +static void test_reshape() {} + +template <typename T, index_t dims1, index_t... dims> +static void test_reshape(const tensor<T, dims1>& t1, const tensor<T, dims>&... ts) +{ + cforeach(std::make_tuple((&ts)...), + [&](auto t2) + { + test_reshape_body(t1, *t2); + test_reshape_body(*t2, t1); + }); + + test_reshape(ts...); +} + TEST(xreshape) { std::array<float, 12> x; tprocess(x_reshape(x, shape{ 3, 4 }), tcounter<float, 2>{ 0, { 10, 1 } }); CHECK(x == std::array<float, 12>{ { 0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23 } }); + + test_reshape(tensor<float, 1>{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }, // + tensor<float, 2>{ { 0, 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10, 11 } }, + tensor<float, 2>{ { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } }, + tensor<float, 2>{ { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 9, 10, 11 } }, + tensor<float, 2>{ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 8, 9 }, { 10, 11 } }, + tensor<float, 2>{ + { 0 }, { 1 }, { 2 }, { 3 }, { 4 }, { 5 }, { 6 }, { 7 }, { 8 }, { 9 }, { 10 }, { 11 } }); + + test_reshape(tensor<float, 1>{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }, // + tensor<float, 3>{ { { 0, 1 }, { 2, 3 }, { 4, 5 } }, { { 6, 7 }, { 8, 9 }, { 10, 11 } } }, + tensor<float, 4>{ { { { 0 }, { 1 } }, { { 2 }, { 3 } }, { { 4 }, { 5 } } }, + { { { 6 }, { 7 } }, { { 8 }, { 9 } }, { { 10 }, { 11 } } } }); + + test_reshape( + tensor<float, 1>{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }, // + tensor<float, 3>{ { { 0, 1 }, { 2, 3 }, { 4, 5 } }, + { { 6, 7 }, { 8, 9 }, { 10, 11 } }, + { { 12, 13 }, { 14, 15 }, { 16, 17 } }, + { { 18, 19 }, { 20, 21 }, { 22, 23 } } }, + tensor<float, 4>{ + { { { 0, 1 }, { 2, 3 } }, { { 4, 5 }, { 6, 7 } }, { { 8, 9 }, { 10, 11 } } }, + { { { 12, 13 }, { 14, 15 } }, { { 16, 17 }, { 18, 19 } }, { { 20, 21 }, { 22, 23 } } } }); } } // namespace CMT_ARCH_NAME @@ -534,21 +682,11 @@ extern "C" __declspec(dllexport) void assembly_test12( const xfunction<std::plus<>, std::array<std::array<uint32_t, 1>, 4>&, std::array<std::array<uint32_t, 4>, 1>&>& y) { - // [[maybe_unused]] constexpr auto sh1 = expression_traits<decltype(x)>::shapeof(); - // [[maybe_unused]] constexpr auto sh2 = expression_traits<decltype(y)>::shapeof(); - - // static_assert(sh1 == shape{ 4, 4 }); - // static_assert(sh2 == shape{ 4, 4 }); tprocess(x, y); } extern "C" __declspec(dllexport) void assembly_test13(const tensor<float, 1>& x, const tensor<float, 1>& y) { - // [[maybe_unused]] constexpr auto sh1 = expression_traits<decltype(x)>::shapeof(); - // [[maybe_unused]] constexpr auto sh2 = expression_traits<decltype(y)>::shapeof(); - - // static_assert(sh1 == shape{ 4, 4 }); - // static_assert(sh2 == shape{ 4, 4 }); tprocess(x, y * 0.5f); } @@ -653,6 +791,58 @@ TEST(xwitharguments) static_assert(std::is_same_v<decltype(fn3)::nth<0>, const val&>); } +TEST(slices) +{ + const auto _ = std::nullopt; + tensor<float, 1> t1{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + CHECK(t1(tstart(3)) == tensor<float, 1>{ 3, 4, 5, 6, 7, 8, 9 }); + CHECK(t1(tstop(3)) == tensor<float, 1>{ 0, 1, 2 }); + CHECK(t1(trange(3, 7)) == tensor<float, 1>{ 3, 4, 5, 6 }); + + CHECK(t1(tstart(10)) == tensor<float, 1>{}); + CHECK(t1(tstop(0)) == tensor<float, 1>{}); + CHECK(t1(trange(7, 3)) == tensor<float, 1>{}); + + CHECK(t1(tstart(-2)) == tensor<float, 1>{ 8, 9 }); + CHECK(t1(trange(-7, -4)) == tensor<float, 1>{ 3, 4, 5 }); + CHECK(t1(tall()) == tensor<float, 1>{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + + CHECK(t1(trange(3, _)) == tensor<float, 1>{ 3, 4, 5, 6, 7, 8, 9 }); + CHECK(t1(trange(_, 7)) == tensor<float, 1>{ 0, 1, 2, 3, 4, 5, 6 }); + + CHECK(t1(trange(_, _, 2)) == tensor<float, 1>{ 0, 2, 4, 6, 8 }); + CHECK(t1(trange(_, _, 5)) == tensor<float, 1>{ 0, 5 }); + CHECK(t1(trange(_, _, 12)) == tensor<float, 1>{ 0 }); + CHECK(t1(trange(1, _, 2)) == tensor<float, 1>{ 1, 3, 5, 7, 9 }); + CHECK(t1(trange(1, _, 5)) == tensor<float, 1>{ 1, 6 }); + CHECK(t1(trange(1, _, 12)) == tensor<float, 1>{ 1 }); + + CHECK(t1(tstep(2))(tstep(2)) == tensor<float, 1>{ 0, 4, 8 }); + CHECK(t1(tstep(2))(tstep(2))(tstep(2)) == tensor<float, 1>{ 0, 8 }); + CHECK(t1(tstep(2))(tstep(3)) == tensor<float, 1>{ 0, 6 }); + + CHECK(t1(trange(_, _, -1)) == tensor<float, 1>{ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }); + CHECK(t1(trange(5, _, -1)) == tensor<float, 1>{ 5, 4, 3, 2, 1, 0 }); + CHECK(t1(trange(1, 0, -1)) == tensor<float, 1>{ 1 }); + + CHECK(t1(trange(3, 3 + 12, 0)) == tensor<float, 1>{ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }); +} + +TEST(from_ilist) +{ + tensor<float, 1> t1{ 10, 20, 30, 40 }; + CHECK(t1 == tensor<float, 1>(shape{ 4 }, { 10, 20, 30, 40 })); + + tensor<float, 2> t2{ { 10, 20 }, { 30, 40 } }; + CHECK(t2 == tensor<float, 2>(shape{ 2, 2 }, { 10, 20, 30, 40 })); + + tensor<float, 2> t3{ { 10, 20 } }; + CHECK(t3 == tensor<float, 2>(shape{ 1, 2 }, { 10, 20 })); + + tensor<float, 3> t4{ { { 10, 20 }, { 30, 40 } }, { { 50, 60 }, { 70, 80 } } }; + CHECK(t4 == tensor<float, 3>(shape{ 2, 2, 2 }, { 10, 20, 30, 40, 50, 60, 70, 80 })); +} + TEST(enumerate) { CHECK(enumerate(vec_shape<int, 4>{}, 4) == vec{ 0, 4, 8, 12 });