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 35d5c0c5c1718f076f541c96be5f40e7dd00c04c
parent 0e2acdf098b243683853f777f3ee864389f630b4
Author: d.levin256@gmail.com <d.levin256@gmail.com>
Date:   Sun, 21 Oct 2018 18:55:26 +0300

Merge branch 'dev'

Diffstat:
M.gitignore | 10++++++++--
M.travis.yml | 6+++++-
Mexamples/biquads.cpp | 30+++++++++++++++++++++++++++++-
Minclude/kfr/base/filter.hpp | 40++++++++++++++++++++++++++++++++++++++++
Minclude/kfr/base/pointer.hpp | 297++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Minclude/kfr/dsp/biquad.hpp | 33++++++++++++++++++++++++---------
Minclude/kfr/io/python_plot.hpp | 26+++++++++++++++-----------
Mtests/CMakeLists.txt | 3+++
Mtests/expression_test.cpp | 24++++++++++++++++++++----
9 files changed, 318 insertions(+), 151 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -69,6 +69,8 @@ var/ *.egg-info/ .installed.cfg *.egg +venv/ + # Sphinx documentation docs/ @@ -76,5 +78,10 @@ docs/ # CLion .idea/ +# VSCode +.vs-code/ + +# CLion +cmake-* -*.TMP -\ No newline at end of file +*.TMP diff --git a/.travis.yml b/.travis.yml @@ -43,11 +43,15 @@ matrix: osx_image: xcode9.4 env: - TEST=XCODE9.4 CMAKEARGS="-DCMAKE_BUILD_TYPE=Release .." + - os: osx + osx_image: xcode10 + env: + - TEST=XCODE10.0 CMAKEARGS="-DCMAKE_BUILD_TYPE=Release .." before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cmake --version || brew install cmake; fi - cmake --version - + script: - mkdir build - cd build diff --git a/examples/biquads.cpp b/examples/biquads.cpp @@ -20,7 +20,7 @@ int main() { biquad_params<fbase> bq[] = { biquad_notch(0.1, 0.5), biquad_notch(0.2, 0.5), biquad_notch(0.3, 0.5), biquad_notch(0.4, 0.5) }; - output = biquad(bq, unitimpulse()); + output = biquad(bq, unitimpulse()); } plot_save("biquad_notch", output, options + ", title='Four Biquad Notch filters'"); @@ -66,5 +66,33 @@ int main() } plot_save("biquad_bandpass", output, options + ", title='Biquad band pass (0.25, 0.2)'"); + { + // filter initialization + biquad_params<fbase> bq[] = { biquad_lowpass(0.2, 0.9) }; + expression_filter<fbase> filter = to_filter(biquad(bq, placeholder<fbase>())); + + // prepare data + output = unitimpulse(); + + // apply filter + filter.apply(output); + } + plot_save("biquad_custom_filter_lowpass", output, + options + ", title='Biquad Low pass filter (0.2, 0.9) (using expression_filter)'"); + + { + // filter initialization + biquad_params<fbase> bq[] = { biquad_lowpass(0.2, 0.9) }; + biquad_filter<fbase> filter(bq); + + // prepare data + output = unitimpulse(); + + // apply filter + filter.apply(output); + } + plot_save("biquad_filter_lowpass", output, + options + ", title='Biquad Low pass filter (0.2, 0.9) (using biquad_filter)'"); + return 0; } diff --git a/include/kfr/base/filter.hpp b/include/kfr/base/filter.hpp @@ -26,6 +26,7 @@ #pragma once #include "basic_expressions.hpp" +#include "expression.hpp" #include "pointer.hpp" #include "univector.hpp" @@ -101,4 +102,43 @@ protected: virtual void process_buffer(T* dest, const T* src, size_t size) = 0; virtual void process_expression(T* dest, const expression_pointer<T>& src, size_t size) = 0; }; + +template <typename T> +class expression_filter : public filter<T> +{ +public: + template <typename... Args> + explicit expression_filter(expression_pointer<T>&& filter_expr) : filter_expr(std::move(filter_expr)) + { + } + +protected: + void process_buffer(T* dest, const T* src, size_t size) override + { + substitute(filter_expr, to_pointer(make_univector(src, size))); + process(make_univector(dest, size), filter_expr, 0, size); + } + void process_expression(T* dest, const expression_pointer<T>& src, size_t size) override + { + substitute(filter_expr, src); + process(make_univector(dest, size), filter_expr, 0, size); + } + + expression_pointer<T> filter_expr; +}; + +/// @brief Converts expression with placeholder to filter. Placeholder and filter must have the same type +template <typename E, typename T = value_type_of<E>> +KFR_SINTRIN expression_filter<T> to_filter(E&& e) +{ + return expression_filter<T>(to_pointer(std::move(e))); } + +/// @brief Converts expression with placeholder to filter. Placeholder and filter must have the same type +template <typename T, typename E> +KFR_SINTRIN expression_filter<T> to_filter(expression_pointer<T>&& e) +{ + return expression_filter<T>(std::move(e)); +} + +} // namespace kfr diff --git a/include/kfr/base/pointer.hpp b/include/kfr/base/pointer.hpp @@ -34,13 +34,79 @@ namespace kfr constexpr size_t maximum_expression_width = platform<float>::vector_capacity / 4; -constexpr size_t expression_vtable_size = 3 + ilog2(maximum_expression_width) + 1; +template <typename T, bool enable_resource = true> +struct expression_pointer; -template <typename T> -using expression_vtable = std::array<void*, expression_vtable_size>; +namespace internal +{ + +template <typename Expression, typename T, size_t key = 0> +KFR_SINTRIN bool invoke_substitute(Expression& expr, expression_pointer<T>&& new_pointer, + csize_t<key> = csize_t<key>{}); + +} + +template <typename T, size_t N = maximum_expression_width> +struct expression_vtable : expression_vtable<T, N / 2> +{ + using func_get = void (*)(void*, size_t, vec<T, N>&); + func_get get; + + template <typename Expression> + expression_vtable(ctype_t<Expression> t) + : expression_vtable<T, N / 2>(t), get(&expression_vtable<T, N>::template static_get<Expression>) + { + } + + template <typename Expression> + static void static_get(void* instance, size_t index, vec<T, N>& result) + { + result = static_cast<Expression*>(instance)->operator()(cinput, index, vec_t<T, N>()); + } +}; -struct dummy_content +template <typename T> +struct expression_vtable<T, 0> { + using func_size = size_t (*)(void* p); + using func_begin_block = void (*)(void*, size_t); + using func_end_block = void (*)(void*, size_t); + using func_substitute = bool (*)(void*, expression_pointer<T>&&); + + func_size size; + func_begin_block begin_block; + func_end_block end_block; + func_substitute substitute; + + template <typename Expression> + expression_vtable(ctype_t<Expression> t) + : size(&expression_vtable<T, 0>::template static_size<Expression>), + begin_block(&expression_vtable<T, 0>::template static_begin_block<Expression>), + end_block(&expression_vtable<T, 0>::template static_end_block<Expression>), + substitute(&expression_vtable<T, 0>::template static_substitute<Expression>) + { + } + + template <typename Expression> + static size_t static_size(void* instance) + { + return static_cast<Expression*>(instance)->size(); + } + template <typename Expression> + static void static_begin_block(void* instance, size_t size) + { + return static_cast<Expression*>(instance)->begin_block(cinput, size); + } + template <typename Expression> + static void static_end_block(void* instance, size_t size) + { + return static_cast<Expression*>(instance)->end_block(cinput, size); + } + template <typename Expression> + static bool static_substitute(void* instance, expression_pointer<T>&& ptr) + { + return internal::invoke_substitute(*static_cast<Expression*>(instance), std::move(ptr)); + } }; struct expression_resource @@ -48,25 +114,27 @@ struct expression_resource virtual ~expression_resource() {} virtual void* instance() { return nullptr; } }; + template <typename E> struct alignas(const_max(size_t(8), alignof(E))) expression_resource_impl : expression_resource { expression_resource_impl(E&& e) noexcept : e(std::move(e)) {} virtual ~expression_resource_impl() {} virtual void* instance() override final { return &e; } + private: E e; }; template <typename E> -std::shared_ptr<expression_resource> make_resource(E&& e) +KFR_SINTRIN std::shared_ptr<expression_resource> make_resource(E&& e) { using T = expression_resource_impl<decay<E>>; return std::static_pointer_cast<expression_resource>( std::allocate_shared<T>(allocator<T>(), std::move(e))); } -template <typename T, bool enable_resource = true> +template <typename T, bool enable_resource> struct expression_pointer : input_expression { using value_type = T; @@ -80,150 +148,45 @@ struct expression_pointer : input_expression template <size_t N, KFR_ENABLE_IF(N <= maximum_expression_width)> CMT_INLINE vec<T, N> operator()(cinput_t, size_t index, vec_t<T, N>) const { - using func_t = void (*)(void*, size_t, vec<T, N>&); - static_assert(is_poweroftwo(N), "N must be a power of two"); - constexpr size_t findex = ilog2(N); - func_t func = reinterpret_cast<func_t>((*vtable)[3 + findex]); vec<T, N> result; - func(instance, index, result); + static_cast<const expression_vtable<T, N>*>(vtable)->get(instance, index, result); return result; } template <size_t N, KFR_ENABLE_IF(N > maximum_expression_width)> CMT_INLINE vec<T, N> operator()(cinput_t cinput, size_t index, vec_t<T, N>) const { - return concat(operator()(cinput, index, vec_t<T, N / 2>()), - operator()(cinput, index + N / 2, vec_t<T, N / 2>())); - } - CMT_INLINE void begin_block(cinput_t, size_t size) const - { - using func_t = void (*)(void*, size_t); - func_t func = reinterpret_cast<func_t>((*vtable)[0]); - func(instance, size); - } - CMT_INLINE void end_block(cinput_t, size_t size) const - { - using func_t = void (*)(void*, size_t); - func_t func = reinterpret_cast<func_t>((*vtable)[1]); - func(instance, size); - } - CMT_INLINE size_t size() const - { - using func_t = size_t (*)(void*); - func_t func = reinterpret_cast<func_t>((*vtable)[2]); - return func(instance); + return concat(operator()(cinput, index, vec_t<T, N / 2>()), operator()(cinput, index + N / 2, + vec_t<T, N / 2>())); } + CMT_INLINE void begin_block(cinput_t, size_t size) const { vtable->begin_block(instance, size); } + CMT_INLINE void end_block(cinput_t, size_t size) const { vtable->end_block(instance, size); } + CMT_INLINE size_t size() const { return vtable->size(instance); } -private: - void* instance; - const expression_vtable<T>* vtable; - std::shared_ptr<expression_resource> resource; -}; - -template <typename T> -struct expression_pointer<T, false> : input_expression -{ - using value_type = T; - - expression_pointer() noexcept : instance(nullptr), vtable(nullptr) {} - expression_pointer(void* instance, const expression_vtable<T>* vtable) - : instance(instance), vtable(vtable) + CMT_INLINE bool substitute(expression_pointer<T>&& new_pointer, csize_t<0> = csize_t<0>{}) const { + return vtable->substitute(instance, std::move(new_pointer)); } - template <size_t N, KFR_ENABLE_IF(N <= maximum_expression_width)> - CMT_INLINE vec<T, N> operator()(cinput_t, size_t index, vec_t<T, N>) const - { - using func_t = void (*)(void*, size_t, vec<T, N>&); - static_assert(is_poweroftwo(N), "N must be a power of two"); - constexpr size_t findex = ilog2(N); - func_t func = reinterpret_cast<func_t>((*vtable)[3 + findex]); - vec<T, N> result; - func(instance, index, result); - return result; - } - template <size_t N, KFR_ENABLE_IF(N > maximum_expression_width)> - CMT_INLINE vec<T, N> operator()(cinput_t input, size_t index, vec_t<T, N>) const - { - return concat(operator()(cinput, index, vec_t<T, N / 2>()), - operator()(cinput, index + N / 2, vec_t<T, N / 2>())); - } - CMT_INLINE void begin_block(cinput_t, size_t size) const - { - using func_t = void (*)(void*, size_t); - func_t func = reinterpret_cast<func_t>((*vtable)[0]); - func(instance, size); - } - CMT_INLINE void end_block(cinput_t, size_t size) const - { - using func_t = void (*)(void*, size_t); - func_t func = reinterpret_cast<func_t>((*vtable)[1]); - func(instance, size); - } - CMT_INLINE size_t size() const - { - using func_t = size_t (*)(void*); - func_t func = reinterpret_cast<func_t>((*vtable)[2]); - return func(instance); - } + explicit operator bool() const { return instance != nullptr; } private: void* instance; const expression_vtable<T>* vtable; + std::shared_ptr<expression_resource> resource; }; namespace internal { -template <typename T, size_t N, typename Fn, typename Ret = vec<T, N>, - typename NonMemFn = void (*)(void*, size_t, Ret&)> -CMT_INLINE NonMemFn make_expression_func() -{ - return [](void* fn, size_t index, Ret& result) { - result = (reinterpret_cast<Fn*>(fn)->operator()(cinput, index, vec_t<T, N>())); - }; -} - -template <typename Fn, typename NonMemFn = void (*)(void*, size_t)> -CMT_INLINE NonMemFn make_expression_begin_block() -{ - return [](void* fn, size_t size) { reinterpret_cast<Fn*>(fn)->begin_block(cinput, size); }; -} -template <typename Fn, typename NonMemFn = void (*)(void*, size_t)> -CMT_INLINE NonMemFn make_expression_end_block() -{ - return [](void* fn, size_t size) { reinterpret_cast<Fn*>(fn)->end_block(cinput, size); }; -} -template <typename Fn, typename NonMemFn = size_t (*)(void*)> -CMT_INLINE NonMemFn make_expression_size() -{ - return [](void* fn) -> size_t { return reinterpret_cast<Fn*>(fn)->size(); }; -} - -template <typename T, typename E> -expression_vtable<T> make_expression_vtable_impl() -{ - expression_vtable<T> result; - - result[0] = reinterpret_cast<void*>(internal::make_expression_begin_block<decay<E>>()); - result[1] = reinterpret_cast<void*>(internal::make_expression_end_block<decay<E>>()); - result[2] = reinterpret_cast<void*>(internal::make_expression_size<decay<E>>()); - - cforeach(csizeseq_t<expression_vtable_size - 3>(), [&](auto u) { - constexpr size_t N = 1 << val_of(decltype(u)()); - result[3 + val_of(decltype(u)())] = - reinterpret_cast<void*>(internal::make_expression_func<T, N, decay<E>>()); - }); - return result; -} template <typename T, typename E> CMT_INLINE expression_vtable<T>* make_expression_vtable() { static_assert(is_input_expression<E>::value, "E must be an expression"); - static expression_vtable<T> vtable = internal::make_expression_vtable_impl<T, E>(); + static expression_vtable<T> vtable{ ctype_t<decay<E>>{} }; return &vtable; } -} +} // namespace internal /** @brief Converts the given expression into an opaque object. * This overload takes reference to the expression. @@ -247,4 +210,92 @@ CMT_INLINE expression_pointer<T> to_pointer(E&& expr) std::shared_ptr<expression_resource> ptr = make_resource(std::move(expr)); return expression_pointer<T>(ptr->instance(), internal::make_expression_vtable<T, E>(), std::move(ptr)); } + +template <typename T, size_t key> +class expression_placeholder : public input_expression +{ +public: + using value_type = T; + expression_placeholder() noexcept = default; + template <typename U, size_t N> + CMT_INLINE vec<U, N> operator()(cinput_t, size_t index, vec_t<U, N>) const + { + return pointer ? cast<U>(pointer(cinput, index, vec_t<T, N>())) : 0; + } + expression_pointer<T> pointer; +}; + +template <typename T, size_t key = 0> +KFR_SINTRIN expression_placeholder<T, key> placeholder(csize_t<key> = csize_t<key>{}) +{ + return expression_placeholder<T, key>(); +} + +template <typename... Args> +KFR_SINTRIN bool substitute(input_expression&, Args&&...) +{ + return false; } + +namespace internal +{ +template <typename... Args, typename T, size_t key, size_t... indices> +KFR_SINTRIN bool substitute(internal::expression_base<Args...>& expr, expression_pointer<T>&& new_pointer, + csize_t<key>, csizes_t<indices...>); + +} + +template <typename T, size_t key = 0> +KFR_SINTRIN bool substitute(expression_placeholder<T, key>& expr, expression_pointer<T>&& new_pointer, + csize_t<key> = csize_t<key>{}) +{ + expr.pointer = std::move(new_pointer); + return true; +} + +template <typename... Args, typename T, size_t key = 0> +KFR_SINTRIN bool substitute(internal::expression_base<Args...>& expr, expression_pointer<T>&& new_pointer, + csize_t<key> = csize_t<key>{}) +{ + return internal::substitute(expr, std::move(new_pointer), csize_t<key>{}, + indicesfor_t<Args...>{}); +} + +template <typename T, size_t key = 0> +KFR_SINTRIN bool substitute(expression_pointer<T>& expr, expression_pointer<T>&& new_pointer, + csize_t<key> = csize_t<key>{}) +{ + return expr.substitute(std::move(new_pointer), csize_t<key>{}); +} + +namespace internal +{ + +KFR_SINTRIN bool var_or() { return false; } + +template <typename... Args> +KFR_SINTRIN bool var_or(bool b, Args... args) +{ + return b || var_or(args...); +} + +template <typename... Args, typename T, size_t key, size_t... indices> +KFR_SINTRIN bool substitute(internal::expression_base<Args...>& expr, expression_pointer<T>&& new_pointer, + csize_t<key>, csizes_t<indices...>) +{ + return var_or(substitute(std::get<indices>(expr.args), std::move(new_pointer), csize_t<key>())...); +} + +} // namespace internal + +namespace internal +{ + +template <typename Expression, typename T, size_t key> +KFR_SINTRIN bool invoke_substitute(Expression& expr, expression_pointer<T>&& new_pointer, csize_t<key>) +{ + return kfr::substitute(expr, std::move(new_pointer), csize_t<key>{}); +} + +} // namespace internal +} // namespace kfr diff --git a/include/kfr/dsp/biquad.hpp b/include/kfr/dsp/biquad.hpp @@ -157,8 +157,8 @@ struct expression_biquads_l : public expression_base<E1> const vec<T, filters>& in) { const vec<T, filters> out = bq.b0 * in + state.s1; - state.s1 = state.s2 + bq.b1 * in - bq.a1 * out; - state.s2 = bq.b2 * in - bq.a2 * out; + state.s1 = state.s2 + bq.b1 * in - bq.a1 * out; + state.s2 = bq.b2 * in - bq.a2 * out; return out; } biquad_block<T, filters> bq; @@ -181,7 +181,7 @@ struct expression_biquads : expression_base<E1> for (size_t i = 0; i < filters - 1; i++) { const vec<T, 1> in = i < size ? this->argument_first(cinput, i, vec_t<T, 1>()) : 0; - state.out = process(bq, state, insertleft(in[0], state.out)); + state.out = process(bq, state, insertleft(in[0], state.out)); } } CMT_INLINE void end_block(cinput_t cinput, size_t) const { state = saved_state; } @@ -219,8 +219,8 @@ struct expression_biquads : expression_base<E1> for (; i < std::min(width, block_end - index); i++) { const vec<T, 1> in = this->argument_first(cinput, index + i, vec_t<T, 1>()); - state.out = process(bq, state, insertleft(in[0], state.out)); - out[i] = state.out[filters - 1]; + state.out = process(bq, state, insertleft(in[0], state.out)); + out[i] = state.out[filters - 1]; } saved_state = state; for (; i < width; i++) @@ -235,8 +235,8 @@ struct expression_biquads : expression_base<E1> vec<T, filters> in) { const vec<T, filters> out = bq.b0 * in + state.s1; - state.s1 = state.s2 + bq.b1 * in - bq.a1 * out; - state.s2 = bq.b2 * in - bq.a2 * out; + state.s1 = state.s2 + bq.b1 * in - bq.a1 * out; + state.s2 = bq.b2 * in - bq.a2 * out; return out; } biquad_block<T, filters> bq; @@ -245,7 +245,7 @@ struct expression_biquads : expression_base<E1> mutable biquad_state<T, filters> saved_state; mutable size_t block_end; }; -} +} // namespace internal /** * @brief Returns template expressions that applies biquad filter to the input. @@ -301,4 +301,19 @@ CMT_INLINE expression_pointer<T> biquad(const biquad_params<T>* bq, size_t count }, [&] { return to_pointer(zeros<T>()); }); } -} + +template <typename T> +class biquad_filter : public expression_filter<T> +{ +public: + biquad_filter(const biquad_params<T>* bq, size_t count) + : expression_filter<T>(to_pointer(biquad(bq, count, placeholder<T>()))) + { + } + + template <size_t N> + biquad_filter(const biquad_params<T> (&bq)[N]) : biquad_filter(bq, N) + { + } +}; +} // namespace kfr diff --git a/include/kfr/io/python_plot.hpp b/include/kfr/io/python_plot.hpp @@ -78,7 +78,7 @@ inline T flush_to_zero(T value) { return static_cast<double>(value); } -} +} // namespace internal inline std::string concat_args() { return {}; } @@ -89,15 +89,22 @@ std::string concat_args(const std::string& left, const Ts&... rest) return left.empty() ? right : right.empty() ? left : left + ", " + right; } +inline std::string python_prologue() +{ + return "#!/usr/bin/env python\n" + "import sys\n" + "import os\n" + "sys.path.append(os.path.abspath(__file__ + '/../../../../dspplot/dspplot'))\n" + "sys.path.append(os.path.abspath(__file__ + '/../../../dspplot/dspplot'))\n" + "import dspplotting as dspplot\n\n"; +} + template <int = 0> void plot_show(const std::string& name, const std::string& wavfile, const std::string& options = "") { print(name, "..."); std::string ss; - ss += "#!/usr/bin/env python\n" - "import dspplot\n\n" - "dspplot.plot(" + - concat_args("r'" + wavfile + "'", options) + ")\n"; + ss += python_prologue() + "dspplot.plot(" + concat_args("r'" + wavfile + "'", options) + ")\n"; internal::python(name, ss); print("done\n"); @@ -115,9 +122,7 @@ void plot_show(const std::string& name, T&& x, const std::string& options = "") print(name, "..."); auto array = make_array_ref(std::forward<T>(x)); std::string ss; - ss += "#!/usr/bin/env python\n" - "import dspplot\n\n" - "data = [\n"; + ss += python_prologue() + "data = [\n"; for (size_t i = 0; i < array.size(); i++) ss += as_string(fmt<'g', 20, 17>(internal::flush_to_zero(array[i])), ",\n"); ss += "]\n"; @@ -141,8 +146,7 @@ void perfplot_show(const std::string& name, T1&& data, T2&& labels, const std::s auto array = make_array_ref(std::forward<T1>(data)); auto labels_array = make_array_ref(std::forward<T2>(labels)); std::string ss; - ss += "#!/usr/bin/env python\n"; - ss += "import dspplot\n\n"; + ss += python_prologue(); ss += "data = [\n"; for (size_t i = 0; i < array.size(); i++) { @@ -174,4 +178,4 @@ void perfplot_save(const std::string& name, T1&& data, T2&& labels, const std::s perfplot_show(name, std::forward<T1>(data), std::forward<T2>(labels), concat_args(options, "file='../perf/" + name + ".svg'")); } -} +} // namespace kfr diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt @@ -44,6 +44,9 @@ if (MPFR_FOUND) include_directories(${MPFR_INCLUDE_DIR}) add_executable(transcendental_test transcendental_test.cpp ${KFR_SRC} ${TEST_SRC}) target_link_libraries(transcendental_test ${MPFR_LIBRARIES}) + if (WIN32) + target_link_libraries(transcendental_test gmp) # for MSYS + endif() endif () add_executable(dsp_test dsp_test.cpp ${KFR_SRC} ${TEST_SRC}) add_executable(empty_test empty_test.cpp ${KFR_SRC} ${TEST_SRC}) diff --git a/tests/expression_test.cpp b/tests/expression_test.cpp @@ -63,12 +63,28 @@ TEST(test_arg_replace) { univector<float, 10> v1 = counter(); univector<float, 10> v2 = -counter(); - auto e1 = to_pointer(v1) * 10; - std::get<0>(e1.args) = to_pointer(v2); + auto e1 = to_pointer(v1) * 10; + std::get<0>(e1.args) = to_pointer(v2); CHECK_EXPRESSION(e1, 10, [](size_t i) { return i * -10.0; }); } +TEST(placeholders) +{ + auto expr = 100 * placeholder<float>(); + CHECK_EXPRESSION(expr, infinite_size, [](size_t) { return 0.f; }); + substitute(expr, to_pointer(counter<float>())); + CHECK_EXPRESSION(expr, infinite_size, [](size_t i) { return 100.f * i; }); +} + +TEST(placeholders_pointer) +{ + expression_pointer<float> expr = to_pointer(10 * placeholder<float>()); + CHECK_EXPRESSION(expr, infinite_size, [](size_t) { return 0.f; }); + substitute(expr, to_pointer(counter<float>())); + CHECK_EXPRESSION(expr, infinite_size, [](size_t i) { return 10.f * i; }); +} + TEST(size_calc) { auto a = counter(); @@ -99,7 +115,7 @@ TEST(partition) { { univector<double, 400> output = zeros(); - auto result = partition(output, counter(), 5, 1); + auto result = partition(output, counter(), 5, 1); CHECK(result.count == 5); CHECK(result.chunk_size == 80); @@ -117,7 +133,7 @@ TEST(partition) { univector<double, 400> output = zeros(); - auto result = partition(output, counter(), 5, 160); + auto result = partition(output, counter(), 5, 160); CHECK(result.count == 3); CHECK(result.chunk_size == 160);