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 f18857532d59b2953c45c3d4fa46f437479b176e
parent 8a15c6c56e6b2a845acf569486e45a75813c97dc
Author: [email protected] <[email protected]>
Date:   Sat, 12 Nov 2022 22:34:12 +0000

Documentation: expressions

Diffstat:
Mdocs/docs/expressions.md | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 160 insertions(+), 7 deletions(-)

diff --git a/docs/docs/expressions.md b/docs/docs/expressions.md @@ -1,8 +1,44 @@ # Expressions -Calling functions on arrays of data is performed lazily using C++ template expressions. +In KFR, all data is represented through _Expression_ concept. +Expression is a virtual multidimensional array consisting of elements that can be read (Input expression) or written to (Output expression). +Expression can have specific size or have infinite size in any dimension. Its size may be known at compile time or at runtime only. + +The number of dimensions must be known at compile time. + +Normally, expressions do not own any data and can be seen as _data generators_ with arbitrary algorithm. But classes owning data (`tensor` and `univector`) provide expression interface as well. Since KFR5 you can make expression from any user defined or `std` type. + +Expressions can refer to other expressions as its arguments. +prvalue expressions are captured by value and moved to expression storage. +lvalue expressions are captured by reference. +The latter may cause dangling references if resulting expression is used outside of its arguments scope. As always, `std::move` forces variable to be captured by value. + +The following function creates Expression that represents a virtual 3-dimensional array with elements starting from 0 at $(0,0,0)$ index +and incremented by $1$, $10$ and $100$ along each axis. +``` +counter(0, 1, 10, 100) +``` + +To get a single element from Expression, call `get_element` function. It takes the expression itself and the index. +``` +get_element(counter(0, 1, 10, 100), {1, 2, 3}) == 321 +``` + +Calling functions or operators on expressions or arrays of data is performed lazily in KFR. This allows better optimization and does not require saving temporary data. +Internally a C++ technique called [Expression templates](https://en.wikipedia.org/wiki/Expression_templates) is used but expressions processing is explicitly vectorized in KFR. You can control some aspects of vectorization. + +## univector - 1D data with compatibility with std::array or std::vector + +`univector<T, tag>` contains 1D data. + +## tensor - multidimensional data + +`tensor<T, dims>` contains multidimensional data. + +## Functions and operators + For example, subtracting one univector from another gives expression type, not univector: ```c++ @@ -26,7 +62,7 @@ univector<int, 5> z = x - y; when an expression is assigned to a `univector` variable, expression is evaluated and values are being written to the variable. -Same applies to calling KFR functions on univectors, this doesn't calculate value immediately. Instead, new expression will be created. +Same applies to calling KFR functions on univectors, this doesn't calculate values immediately. Instead, new expression will be created. ```c++ univector<float, 5> x{1, 2, 3, 4, 5}; @@ -34,9 +70,127 @@ sqrt(x); // only constructs an expression univector<float, 5> values = sqrt(x); // constructs an expression and writes data to univector ``` -Input expressions can be read from and output expressions can be written to. Class can be an input and output expression at same time. `univector` is an example of such class. +Element type of an input expressions can be determined by using `expression_value_type<Expr>` (In KFR4 it was `value_type_of<Expr>`). Since KFR5 all expressions have their type specified. + +### Writing custom expressions + +To turn a user-defined class into expression, you should define +`kfr::expression_traits<Class>` and provide typedefs and function to work with it. + +Input expressions must implement `get_elements` function to retrieve elements at given index. + +Output expressions must implement `set_elements` function to set its elements. + +Example of input expression: + +```c++ +#include <kfr/all.hpp> + +namespace kfr +{ + +template <typename T, index_t Size> +struct identity_matrix +{ +}; -Data type of an input expressions can be determined by using `value_type_of<Expression>`. However, not all expressions have their types specified. -In such cases `value_type_of` will return special type `generic`. +template <typename T, index_t Size> +struct expression_traits<identity_matrix<T, Size>> : expression_traits_defaults +{ + // element type for expression + using value_type = T; -Size (length) of an expression also can be specified or not. `counter` is an example of generic (untyped) expression without the size. -\ No newline at end of file + // number of dimensions, must be known at compile time + constexpr static size_t dims = 2; + + // function to retrieve shape (size) of matrix, runtime version + constexpr static shape<2> shapeof(const identity_matrix<T, Size>& self) { return { Size, Size }; } + + // function to retrieve shape (size) of matrix, compile time version + // if the size is unknown at compile time the function must be still defined + // but return undefined_size for every axis with unknown size + constexpr static shape<2> shapeof() { return { Size, Size }; } +}; + +template <typename T, index_t Size, index_t Axis, size_t N> +vec<T, N> get_elements(const identity_matrix<T, Size>& self, const shape<2>& index, + const axis_params<Axis, N>& sh) +{ + // identity matrix expression returns 1 if col==row, 0 otherwise + // indices<A> returns indices for given axis taking vectorization into account + // select is a SIMD-enabled ternary operator function + return select(indices<0>(index, sh) == indices<1>(index, sh), 1, 0); +} + +} + +int main() +{ + using namespace kfr; + // Create identity_matrix expression with 9 cols/rows, + // Render it to tensor class (trender) + // Convert to string with maximum 16 columns of values + // And 0 dimensions collapsed (to_string). + println(trender(identity_matrix<float, 9>{}).to_string(16, 0)); +} + +``` +**Output**: +``` +{{1, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 1, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 1}} +``` + +Expression may be defined in a compact form using only a single class: + +```c++ +template <typename T, index_t Size> +struct identity_matrix : expression_traits_defaults +{ + using value_type = T; + + constexpr static size_t dims = 2; + constexpr static shape<2> shapeof(const identity_matrix& self) { return { Size, Size }; } + constexpr static shape<2> shapeof() { return { Size, Size }; } + + template <index_t Axis, size_t N> + friend vec<T, N> get_elements(const identity_matrix& self, const shape<2>& index, + const axis_params<Axis, N>& sh) + { + return select(indices<0>(index, sh) == indices<1>(index, sh), 1, 0); + } +}; + +``` + +Now with size defined at runtime. + +```c++ +template <typename T> +struct identity_matrix : expression_traits_defaults +{ + using value_type = T; + + constexpr static size_t dims = 2; + constexpr static shape<2> shapeof(const identity_matrix& self) { return { self.size, self.size }; } + // undefined_size means size is not known at compile time + constexpr static shape<2> shapeof() { return { undefined_size, undefined_size }; } + + template <index_t Axis, size_t N> + friend vec<T, N> get_elements(const identity_matrix& self, const shape<2>& index, + const axis_params<Axis, N>& sh) + { + return select(indices<0>(index, sh) == indices<1>(index, sh), 1, 0); + } + + index_t size; +}; + +```