From 7f350bdf534777b3c6da16cb58128fa51f3561fb Mon Sep 17 00:00:00 2001 From: Vincent Samy Date: Thu, 25 Oct 2018 18:43:21 +0900 Subject: [PATCH] Header-only lib. Add digital filter. Add Butterworth filter. Add Moving average filter. Add polynomial transformation (compute polynome coeff from roots). Add test units. --- CMakeLists.txt | 12 ++- include/Butterworth.h | 40 ++++++++ include/Butterworth.inl | 92 ++++++++++++++++++ include/CMakeLists.txt | 14 +++ include/DigitalFilter.h | 31 ++++++ include/GenericFilter.h | 54 +++++------ .../GenericFilter.inl | 95 ++++++++----------- include/MovingAverage.h | 20 ++++ include/fratio.h | 24 +++++ include/polynome_functions.h | 32 +++++++ include/type_checks.h | 15 +++ src/CMakeLists.txt | 13 --- tests/ButterworthFilterTests.cpp | 76 +++++++++++++++ tests/CMakeLists.txt | 12 ++- tests/DigitalFilterTests.cpp | 57 +++++++++++ tests/FilterTest.cpp | 61 ------------ tests/GenericFilterTests.cpp | 16 ++++ tests/MovingAverageFilterTests.cpp | 43 +++++++++ tests/polynome_functions_tests.cpp | 79 +++++++++++++++ tests/warning_macro.h | 23 +++++ 20 files changed, 643 insertions(+), 166 deletions(-) create mode 100644 include/Butterworth.h create mode 100644 include/Butterworth.inl create mode 100644 include/CMakeLists.txt create mode 100644 include/DigitalFilter.h rename src/GenericFilter.cpp => include/GenericFilter.inl (52%) create mode 100644 include/MovingAverage.h create mode 100644 include/fratio.h create mode 100644 include/polynome_functions.h create mode 100644 include/type_checks.h delete mode 100644 src/CMakeLists.txt create mode 100644 tests/ButterworthFilterTests.cpp create mode 100644 tests/DigitalFilterTests.cpp delete mode 100644 tests/FilterTest.cpp create mode 100644 tests/GenericFilterTests.cpp create mode 100644 tests/MovingAverageFilterTests.cpp create mode 100644 tests/polynome_functions_tests.cpp create mode 100644 tests/warning_macro.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ff09b19..1fb6b98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # Version minimum -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.0.2) include(cmake/base.cmake) include(cmake/eigen.cmake) @@ -26,11 +26,13 @@ if(MSVC) set(CMAKE_MSVCIDE_RUN_PATH "\$(SolutionDir)/src/\$(Configuration)") endif() -# Eigen -set(Eigen_REQUIRED "eigen3 >= 3.3") -search_for_eigen() +add_compile_options("-D_USE_MATH_DEFINES") -add_subdirectory(src) +# Eigen +# set(Eigen_REQUIRED "eigen3 >= 3.3") +# search_for_eigen() + +add_subdirectory(include) if(NOT ${DISABLE_TESTS}) add_subdirectory(tests) diff --git a/include/Butterworth.h b/include/Butterworth.h new file mode 100644 index 0000000..0594a41 --- /dev/null +++ b/include/Butterworth.h @@ -0,0 +1,40 @@ +#pragma once + +#include "GenericFilter.h" +#include +#include + +namespace fratio { + +// https://www.dsprelated.com/showarticle/1119.php +template +class Butterworth : public GenericFilter { +public: + enum class Type { + LowPass + }; + + // public: + // static double minimumRequiredFrequency(...); +public: + Butterworth(Type type = Type::LowPass); + Butterworth(size_t order, T fc, T fs, Type type = Type::LowPass); + + void setFilterParameters(size_t order, T fc, T fs); + +private: + void initialize(size_t order, T fc, T fs); + void computeDigitalRep(); + void updateCoeffSize(); + +private: + Type m_type; + size_t m_order; + T m_fc; + T m_fs; + std::vector> m_poles; +}; + +} // namespace fratio + +#include "Butterworth.inl" \ No newline at end of file diff --git a/include/Butterworth.inl b/include/Butterworth.inl new file mode 100644 index 0000000..4f12f92 --- /dev/null +++ b/include/Butterworth.inl @@ -0,0 +1,92 @@ +#include "polynome_functions.h" +#include +#include + +namespace fratio { + +template +Butterworth::Butterworth(Type type) + : m_type(type) +{ +} + +template +Butterworth::Butterworth(size_t order, T fc, T fs, Type type) + : m_type(type) +{ + initialize(order, fc, fs); +} + +template +void Butterworth::setFilterParameters(size_t order, T fc, T fs) +{ + initialize(order, fc, fs); +} + +template +void Butterworth::initialize(size_t order, T fc, T fs) +{ + if (m_fc > m_fs / 2.) { + std::stringstream ss; + ss << "The cut-off-frequency must be inferior to the sampling frequency" + << "\n Given cut-off-frequency is " << m_fc + << "\n Given sample frequency is " << m_fs; + throw std::runtime_error(ss.str()); + } + + m_order = order; + m_fc = fc; + m_fs = fs; + m_poles.resize(order); + updateCoeffSize(); + computeDigitalRep(); +} + +template +void Butterworth::computeDigitalRep() +{ + T pi = static_cast(M_PI); + // Continuous pre-warped frequency + T fpw = (m_fs / pi) * std::tan(pi * m_fc / m_fs); + T scaleFactor = T(2) * pi * fpw; + + auto thetaK = [pi, order = m_order](size_t k) -> T { + return (T(2) * k - T(1)) * pi / (T(2) * order); + }; + + // Compute poles + std::complex scalePole; + for (size_t k = 1; k <= m_order; ++k) { + scalePole = scaleFactor * std::complex(-std::sin(thetaK(k)), std::cos(thetaK(k))); + scalePole /= T(2) * m_fs; + m_poles[k - 1] = (T(1) + scalePole) / (T(1) - scalePole); + } + + std::vector> numPoles(m_order, std::complex(-1)); + std::vector> a = VietaAlgo>::polyCoeffFromRoot(m_poles); + std::vector> b = VietaAlgo>::polyCoeffFromRoot(numPoles); + T norm = 0; + T sumB = 0; + for (size_t i = 0; i < m_order + 1; ++i) { + m_aCoeff[i] = a[i].real(); + m_bCoeff[i] = b[i].real(); + norm += m_aCoeff[i]; + sumB += m_bCoeff[i]; + } + + norm /= sumB; + for (auto& b : m_bCoeff) + b *= norm; + + checkCoeff(m_aCoeff, m_bCoeff); +} + +template +void Butterworth::updateCoeffSize() +{ + m_aCoeff.resize(m_order + 1); + m_bCoeff.resize(m_order + 1); + resetFilter(); +} + +} // namespace fratio \ No newline at end of file diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..94ab49f --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,14 @@ +set(HEADERS + Butterworth.h + DigitalFilter.h + GenericFilter.h + MovingAverage.h + polynome_functions.h +) + +add_library(${PROJECT_NAME} INTERFACE) +target_include_directories(${PROJECT_NAME} INTERFACE ${HEADERS}) +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) \ No newline at end of file diff --git a/include/DigitalFilter.h b/include/DigitalFilter.h new file mode 100644 index 0000000..903f5fa --- /dev/null +++ b/include/DigitalFilter.h @@ -0,0 +1,31 @@ +#pragma once + +#include "GenericFilter.h" + +namespace fratio { + +template +class DigitalFilter : public GenericFilter { +public: + DigitalFilter() = default; + DigitalFilter(const std::vector& aCoeff, const std::vector& bCoeff) + : GenericFilter(aCoeff, bCoeff) + { + } + + void setCoeff(const std::vector& aCoeff, const std::vector& bCoeff) + { + checkCoeff(aCoeff, bCoeff); + + m_aCoeff = aCoeff; + m_bCoeff = bCoeff; + resetFilter(); + + normalize(); + } + + size_t aOrder() const noexcept { return m_aCoeff.size(); } + size_t bOrder() const noexcept { return m_bCoeff.size(); } +}; + +} // namespace fratio \ No newline at end of file diff --git a/include/GenericFilter.h b/include/GenericFilter.h index fbc3638..d744153 100644 --- a/include/GenericFilter.h +++ b/include/GenericFilter.h @@ -1,44 +1,40 @@ #pragma once +#include "type_checks.h" #include #include namespace fratio { -class GenericFilter { +template ::value && !std::is_const::value>> +class GenericFilter; + +template +class GenericFilter { public: - GenericFilter() = default; - GenericFilter(size_t nData); - GenericFilter(size_t nData, const std::vector& aCoeff, const std::vector& bCoeff); - - void setNData(size_t nData); - void setCoeff(const std::vector& aCoeff, const std::vector& bCoeff); - void getCoeff(std::vector& aCoeff, std::vector& bCoeff) const noexcept; - size_t aOrder() const noexcept { return m_aCoeff.size(); } - size_t bOrder() const noexcept { return m_bCoeff.size(); } - - // bool stepFilter(const Eigen::VectorXd& data); - // https://stackoverflow.com/questions/50511549/meaning-of-rational-transfer-function-underlying-matlab-filter-or-scipy-signal-f - double stepFilter(double data); - std::vector filter(const std::vector& data); + T stepFilter(T data); + std::vector filter(const std::vector& data); void resetFilter(); - // Eigen::VectorXd results() const noexcept; - // std::vector results() const noexcept { return m_filteredData; } - -private: - void checkCoeffs(const std::vector& aCoeff, const std::vector& bCoeff); - void normalize(); - void shiftData(); + void getCoeff(std::vector& aCoeff, std::vector& bCoeff) const noexcept; protected: - std::vector m_aCoeff; - std::vector m_bCoeff; + GenericFilter() = default; + GenericFilter(const std::vector& aCoeff, const std::vector& bCoeff); + virtual ~GenericFilter() = default; - std::vector m_filteredData; - std::vector m_rawData; - // Eigen::MatrixXd m_filteredData; - // Eigen::MatrixXd m_rawData; + void checkCoeff(const std::vector& aCoeff, const std::vector& bCoeff); + void normalize(); + +protected: + std::vector m_aCoeff; + std::vector m_bCoeff; + +private: + std::vector m_filteredData; + std::vector m_rawData; }; -} // namespace fratio \ No newline at end of file +} // namespace fratio + +#include "GenericFilter.inl" \ No newline at end of file diff --git a/src/GenericFilter.cpp b/include/GenericFilter.inl similarity index 52% rename from src/GenericFilter.cpp rename to include/GenericFilter.inl index 13bbc1a..bda771c 100644 --- a/src/GenericFilter.cpp +++ b/include/GenericFilter.inl @@ -1,47 +1,9 @@ -#include "GenericFilter.h" - -#include - namespace fratio { -GenericFilter::GenericFilter(size_t nData) -{ -} +// Public functions -GenericFilter::GenericFilter(size_t nData, const std::vector& aCoeff, const std::vector& bCoeff) - : m_aCoeff(aCoeff) - , m_bCoeff(bCoeff) - , m_filteredData(aCoeff.size(), 0) - , m_rawData(bCoeff.size(), 0) -{ - checkCoeffs(aCoeff, bCoeff); - normalize(); -} - -void GenericFilter::setNData(size_t nData) -{ - // m_filteredData.resize(nData, m_filteredData.cols()); - // m_rawData.resize(nData, nData.cols()); -} - -void GenericFilter::setCoeff(const std::vector& aCoeff, const std::vector& bCoeff) -{ - checkCoeffs(aCoeff, bCoeff); - - m_aCoeff = aCoeff; - m_bCoeff = bCoeff; - resetFilter(); - - normalize(); -} - -void GenericFilter::getCoeff(std::vector& aCoeff, std::vector& bCoeff) const noexcept -{ - aCoeff = m_aCoeff; - bCoeff = m_bCoeff; -} - -double GenericFilter::stepFilter(double data) +template +T GenericFilter::stepFilter(T data) { // Slide data for (auto rit1 = m_rawData.rbegin(), rit2 = m_rawData.rbegin() + 1; rit2 != m_rawData.rend(); ++rit1, ++rit2) @@ -49,7 +11,7 @@ double GenericFilter::stepFilter(double data) for (auto rit1 = m_filteredData.rbegin(), rit2 = m_filteredData.rbegin() + 1; rit2 != m_filteredData.rend(); ++rit1, ++rit2) *rit1 = *rit2; - double filtData = 0.; + T filtData = 0.; m_rawData[0] = data; for (size_t k = 0; k < m_bCoeff.size(); ++k) @@ -62,36 +24,63 @@ double GenericFilter::stepFilter(double data) return filtData; } -std::vector GenericFilter::filter(const std::vector& data) +template +std::vector GenericFilter::filter(const std::vector& data) { - std::vector results; + std::vector results; results.reserve(data.size()); - for (double d : data) + for (T d : data) results.emplace_back(stepFilter(d)); return results; } -void GenericFilter::resetFilter() +template +void GenericFilter::resetFilter() { m_filteredData.assign(m_aCoeff.size(), 0); m_rawData.assign(m_bCoeff.size(), 0); } -void GenericFilter::normalize() +template +void GenericFilter::getCoeff(std::vector& aCoeff, std::vector& bCoeff) const noexcept { - double a0 = m_aCoeff.front(); - if (std::abs(a0) < 1e-6) // Divide by zero - throw std::invalid_argument("By filtering value for coefficient a0. Should be superior to 1e-6"); + aCoeff = m_aCoeff; + bCoeff = m_bCoeff; +} - for (double& a : m_aCoeff) +// Protected functions + +template +GenericFilter::GenericFilter(const std::vector& aCoeff, const std::vector& bCoeff) + : m_aCoeff(aCoeff) + , m_bCoeff(bCoeff) + , m_filteredData(aCoeff.size(), 0) + , m_rawData(bCoeff.size(), 0) +{ + checkCoeff(aCoeff, bCoeff); + normalize(); +} + +template +void GenericFilter::normalize() +{ + T a0 = m_aCoeff.front(); + if (std::abs(a0) < 1e-8) // Divide by zero + throw std::invalid_argument("By filtering value for coefficient a0. Should be superior to 1e-8"); + + if (std::abs(a0 - T(1)) < 1e-8) + return; + + for (T& a : m_aCoeff) a /= a0; - for (double& b : m_bCoeff) + for (T& b : m_bCoeff) b /= a0; } -void GenericFilter::checkCoeffs(const std::vector& aCoeff, const std::vector& bCoeff) +template +void GenericFilter::checkCoeff(const std::vector& aCoeff, const std::vector& bCoeff) { std::stringstream err; if (aCoeff.size() == 0) diff --git a/include/MovingAverage.h b/include/MovingAverage.h new file mode 100644 index 0000000..0183a9d --- /dev/null +++ b/include/MovingAverage.h @@ -0,0 +1,20 @@ +#pragma once + +#include "GenericFilter.h" + +namespace fratio { + +template +class MovingAverage : public GenericFilter { +public: + MovingAverage() = default; + MovingAverage(size_t windowSize) + : GenericFilter({ T(1) }, std::vector(windowSize, T(1) / windowSize)) + { + } + + void setWindowSize(size_t windowSize) { setCoeff({ T(1) }, std::vector(windowSize, T(1) / windowSize)); } + size_t windowSize() const noexcept { return m_bCoeff.size(); } +}; + +} // namespace fratio \ No newline at end of file diff --git a/include/fratio.h b/include/fratio.h new file mode 100644 index 0000000..0fb47d3 --- /dev/null +++ b/include/fratio.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Butterworth.h" +#include "DigitalFilter.h" +#include "MovingAverage.h" +#include "polynome_functions.h" + +namespace fratio { + +using DigitalFilterf = DigitalFilter; +using DigitalFilterd = DigitalFilter; +using MovingAveragef = MovingAverage; +using MovingAveraged = MovingAverage; +using Butterworthf = Butterworth; +using Butterworthd = Butterworth; + +using VietaAlgof = VietaAlgo; +using VietaAlgod = VietaAlgo; +using VietaAlgoi = VietaAlgo; +using VietaAlgocf = VietaAlgo>; +using VietaAlgocd = VietaAlgo>; +using VietaAlgoci = VietaAlgo>; + +} // namespace fratio \ No newline at end of file diff --git a/include/polynome_functions.h b/include/polynome_functions.h new file mode 100644 index 0000000..d97f4a2 --- /dev/null +++ b/include/polynome_functions.h @@ -0,0 +1,32 @@ +#pragma once + +#include "type_checks.h" +#include +#include + +namespace fratio { + +template ::value || is_complex_t::value>> +struct VietaAlgo; + +template +struct VietaAlgo { + // Vieta's computation: https://en.wikipedia.org/wiki/Vieta%27s_formulas + static std::vector polyCoeffFromRoot(const std::vector& poles); +}; + +template +std::vector VietaAlgo::polyCoeffFromRoot(const std::vector& poles) +{ + std::vector coeff(poles.size() + 1, T(0)); + coeff[0] = T(1); + for (size_t i = 0; i < poles.size(); ++i) { + for (size_t k = i + 1; k > 0; --k) { + coeff[k] = coeff[k] - poles[i] * coeff[k - 1]; + } + } + + return coeff; +} + +} // namespace fratio \ No newline at end of file diff --git a/include/type_checks.h b/include/type_checks.h new file mode 100644 index 0000000..2d71353 --- /dev/null +++ b/include/type_checks.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace fratio { + +template +struct is_complex_t : public std::false_type { +}; + +template +struct is_complex_t> : public std::true_type { +}; + +} // namespace fratio \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index e330769..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -set(HEADERS - ../include/GenericFilter.h -) - -set(SRC - GenericFilter.cpp -) - -add_library(${PROJECT_NAME} ${HEADERS} ${SRC}) -install(TARGETS ${PROJECT_NAME} - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib) \ No newline at end of file diff --git a/tests/ButterworthFilterTests.cpp b/tests/ButterworthFilterTests.cpp new file mode 100644 index 0000000..262b99a --- /dev/null +++ b/tests/ButterworthFilterTests.cpp @@ -0,0 +1,76 @@ +#define BOOST_TEST_MODULE ButterworthFilterTests + +#include "fratio.h" +#include "warning_macro.h" +#include + +DISABLE_CONVERSION_WARNING_BEGIN + +template +struct System { + std::vector data = { 1., 2., 3., 4., 5., 6., 7., 8. }; + size_t order = 5; + T fc = 10; + T fs = 100; + std::vector aCoeffRes = { 1.000000000000000, -2.975422109745684, 3.806018119320413, -2.545252868330468, 0.881130075437837, -0.125430622155356 }; + std::vector bCoeffRes = { 0.001282581078961, 0.006412905394803, 0.012825810789607, 0.012825810789607, 0.006412905394803, 0.001282581078961 }; + std::vector results = { 0.001282581078961, 0.012794287652606, 0.062686244350084, 0.203933712825708, 0.502244959135609, 1.010304217144175, 1.744652693589064, 2.678087381460197 }; +}; + +DISABLE_CONVERSION_WARNING_END + +BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_FILTER_FLOAT, System) +{ + auto gf = fratio::Butterworthf(order, fc, fs); + + std::vector aCoeff, bCoeff, filteredData; + gf.getCoeff(aCoeff, bCoeff); + + BOOST_REQUIRE_EQUAL(aCoeff.size(), aCoeffRes.size()); + BOOST_REQUIRE_EQUAL(bCoeff.size(), bCoeffRes.size()); + BOOST_REQUIRE_EQUAL(aCoeff.size(), bCoeffRes.size()); + + for (size_t i = 0; i < aCoeff.size(); ++i) { + BOOST_CHECK_SMALL(std::abs(aCoeff[i] - aCoeffRes[i]), 1e-4f); + BOOST_CHECK_SMALL(std::abs(bCoeff[i] - bCoeffRes[i]), 1e-4f); + } + + for (float d : data) + filteredData.push_back(gf.stepFilter(d)); + + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-4f); + + gf.resetFilter(); + filteredData = gf.filter(data); + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-4f); +} + +BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_FILTER_DOUBLE, System) +{ + auto gf = fratio::Butterworthd(order, fc, fs); + + std::vector aCoeff, bCoeff, filteredData; + gf.getCoeff(aCoeff, bCoeff); + + BOOST_REQUIRE_EQUAL(aCoeff.size(), aCoeffRes.size()); + BOOST_REQUIRE_EQUAL(bCoeff.size(), bCoeffRes.size()); + BOOST_REQUIRE_EQUAL(aCoeff.size(), bCoeffRes.size()); + + for (size_t i = 0; i < aCoeff.size(); ++i) { + BOOST_CHECK_SMALL(std::abs(aCoeff[i] - aCoeffRes[i]), 1e-14); + BOOST_CHECK_SMALL(std::abs(bCoeff[i] - bCoeffRes[i]), 1e-14); + } + + for (double d : data) + filteredData.push_back(gf.stepFilter(d)); + + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-14); + + gf.resetFilter(); + filteredData = gf.filter(data); + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-14); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8ca87e3..77902d3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,14 +12,16 @@ link_directories(${Boost_LIBRARY_DIR} ${Boost_LIBRARY_DIRS}) macro(addTest testName) add_executable(${testName} ${testName}.cpp) - if(MSVC) - target_link_libraries(${testName} PUBLIC ${PROJECT_NAME}) - else() - target_link_libraries(${testName} PUBLIC ${Boost_LIBRARIES} ${PROJECT_NAME}) + if(NOT MSVC) + target_link_libraries(${testName} PUBLIC ${Boost_LIBRARIES}) endif() add_test(${testName}Unit ${testName}) # Adding a project configuration file (for MSVC only) GENERATE_MSVC_DOT_USER_FILE(${testName}) endmacro(addTest) -addTest(FilterTest) \ No newline at end of file +addTest(ButterWorthFilterTests) +addTest(GenericFilterTests) +addTest(DigitalFilterTests) +addTest(MovingAverageFilterTests) +addTest(polynome_functions_tests) \ No newline at end of file diff --git a/tests/DigitalFilterTests.cpp b/tests/DigitalFilterTests.cpp new file mode 100644 index 0000000..0d2b283 --- /dev/null +++ b/tests/DigitalFilterTests.cpp @@ -0,0 +1,57 @@ +#define BOOST_TEST_MODULE DigitalFilterTests + +#include "fratio.h" +#include "warning_macro.h" +#include + +DISABLE_CONVERSION_WARNING_BEGIN + +template +struct System { + std::vector data = { 1., 2., 3., 4. }; + std::vector aCoeff = { 1., -0.99993717 }; + std::vector bCoeff = { 0.99996859, -0.99996859 }; + std::vector results = { 0.99996859, 1.999874351973491, 2.999717289867956, 3.999497407630634 }; +}; + +DISABLE_CONVERSION_WARNING_END + +BOOST_FIXTURE_TEST_CASE(DIGITAL_FILTER_FLOAT, System) +{ + auto gf = fratio::DigitalFilterf(aCoeff, bCoeff); + BOOST_REQUIRE_EQUAL(aCoeff.size(), gf.aOrder()); + BOOST_REQUIRE_EQUAL(bCoeff.size(), gf.bOrder()); + + std::vector filteredData; + + for (float d : data) + filteredData.push_back(gf.stepFilter(d)); + + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-6f); + + gf.resetFilter(); + filteredData = gf.filter(data); + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-6f); +} + +BOOST_FIXTURE_TEST_CASE(DIGITAL_FILTER_DOUBLE, System) +{ + auto gf = fratio::DigitalFilterd(aCoeff, bCoeff); + BOOST_REQUIRE_EQUAL(aCoeff.size(), gf.aOrder()); + BOOST_REQUIRE_EQUAL(bCoeff.size(), gf.bOrder()); + + std::vector filteredData; + + for (double d : data) + filteredData.push_back(gf.stepFilter(d)); + + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-14); + + gf.resetFilter(); + filteredData = gf.filter(data); + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-14); +} diff --git a/tests/FilterTest.cpp b/tests/FilterTest.cpp deleted file mode 100644 index 97b5840..0000000 --- a/tests/FilterTest.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// This file is part of copra. - -// copra is free software: you can redistribute it and/or -// modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// copra is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with copra. If not, see -// . - -#define BOOST_TEST_MODULE FilterTest - -#include "GenericFilter.h" -#include -#include - -struct System1 { - std::vector data = { 1., 2., 3., 4. }; - std::vector aCoeff = { 1., -0.99993717 }; - std::vector bCoeff = { 0.99996859, -0.99996859 }; - std::vector results = { 0.99996859, 1.999874351973491, 2.999717289867956, 3.999497407630634 }; -}; - -BOOST_AUTO_TEST_CASE(FilterThrows) -{ - BOOST_REQUIRE_THROW(fratio::GenericFilter(1, {}, { 1., 2. }), std::runtime_error); - BOOST_REQUIRE_THROW(fratio::GenericFilter(1, { 1., 2. }, {}), std::runtime_error); - BOOST_REQUIRE_THROW(fratio::GenericFilter(1, { 0. }, { 1. }), std::invalid_argument); - - auto gf = fratio::GenericFilter(); - BOOST_REQUIRE_THROW(gf.setCoeff({}, { 1., 2. }), std::runtime_error); - BOOST_REQUIRE_THROW(gf.setCoeff({ 1., 2. }, {}), std::runtime_error); - BOOST_REQUIRE_THROW(gf.setCoeff({ 0. }, { 1. }), std::invalid_argument); -} - -BOOST_FIXTURE_TEST_CASE(SimpleSystem, System1) -{ - auto gf = fratio::GenericFilter(1, aCoeff, bCoeff); - BOOST_REQUIRE_EQUAL(aCoeff.size(), gf.aOrder()); - BOOST_REQUIRE_EQUAL(bCoeff.size(), gf.bOrder()); - - std::vector filteredData; - - for (double d : data) - filteredData.push_back(gf.stepFilter(d)); - - for (size_t i = 0; i < filteredData.size(); ++i) - BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-14); - - gf.resetFilter(); - filteredData = gf.filter(data); - for (size_t i = 0; i < filteredData.size(); ++i) - BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-14); -} diff --git a/tests/GenericFilterTests.cpp b/tests/GenericFilterTests.cpp new file mode 100644 index 0000000..c527c2a --- /dev/null +++ b/tests/GenericFilterTests.cpp @@ -0,0 +1,16 @@ +#define BOOST_TEST_MODULE GenericFilterTests + +#include "fratio.h" +#include + +BOOST_AUTO_TEST_CASE(FilterThrows) +{ + BOOST_REQUIRE_THROW(fratio::DigitalFilterd({}, { 1., 2. }), std::runtime_error); + BOOST_REQUIRE_THROW(fratio::DigitalFilterd({ 1., 2. }, {}), std::runtime_error); + BOOST_REQUIRE_THROW(fratio::DigitalFilterd({ 0. }, { 1. }), std::invalid_argument); + + auto gf = fratio::DigitalFilterd(); + BOOST_REQUIRE_THROW(gf.setCoeff({}, { 1., 2. }), std::runtime_error); + BOOST_REQUIRE_THROW(gf.setCoeff({ 1., 2. }, {}), std::runtime_error); + BOOST_REQUIRE_THROW(gf.setCoeff({ 0. }, { 1. }), std::invalid_argument); +} diff --git a/tests/MovingAverageFilterTests.cpp b/tests/MovingAverageFilterTests.cpp new file mode 100644 index 0000000..81b887d --- /dev/null +++ b/tests/MovingAverageFilterTests.cpp @@ -0,0 +1,43 @@ +#define BOOST_TEST_MODULE MovingAverageFilterTests + +#include "fratio.h" +#include + +template +struct System { + std::vector data = { 1., 2., 3., 4., 5., 6. }; + size_t windowSize = 4; + std::vector results = { 0.25, 0.75, 1.5, 2.5, 3.5, 4.5 }; +}; + +BOOST_FIXTURE_TEST_CASE(MOVING_AVERAGE_FLOAT, System) +{ + auto ma = fratio::MovingAveragef(windowSize); + std::vector filteredData; + for (float d : data) + filteredData.push_back(ma.stepFilter(d)); + + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-6f); + + ma.resetFilter(); + filteredData = ma.filter(data); + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-6f); +} + +BOOST_FIXTURE_TEST_CASE(MOVING_AVERAGE_DOUBLE, System) +{ + auto ma = fratio::MovingAveraged(windowSize); + std::vector filteredData; + for (double d : data) + filteredData.push_back(ma.stepFilter(d)); + + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-14); + + ma.resetFilter(); + filteredData = ma.filter(data); + for (size_t i = 0; i < filteredData.size(); ++i) + BOOST_CHECK_SMALL(std::abs(filteredData[i] - results[i]), 1e-14); +} diff --git a/tests/polynome_functions_tests.cpp b/tests/polynome_functions_tests.cpp new file mode 100644 index 0000000..bdfac26 --- /dev/null +++ b/tests/polynome_functions_tests.cpp @@ -0,0 +1,79 @@ +#define BOOST_TEST_MODULE polynome_functions_tests + +#include "fratio.h" +#include "warning_macro.h" +#include + +struct SystemInt { + std::vector data = { 1, 1, 1, 1 }; + std::vector results = { 1, -4, 6, -4, 1 }; +}; + +struct SystemCInt { + std::vector> data = { { 1, 1 }, { -1, 4 }, { 12, -3 }, { 5, 2 } }; + std::vector> results = { { 1, 0 }, { -17, -4 }, { 66, 97 }, { 127, -386 }, { -357, 153 } }; +}; + +DISABLE_CONVERSION_WARNING_BEGIN + +template +struct SystemFloat { + std::vector data = { 0.32, -0.0518, 41.4, 0.89 }; + std::vector results = { 1., -42.558199999999999, 48.171601999999993, -9.181098159999999, -0.610759296 }; +}; + +template +struct SystemCFloat { + std::vector> data = { { 1.35, 0.2 }, { -1.5, 4.45 }, { 12.8, -3.36 }, { 5.156, 2.12 } }; + std::vector> results = { { 1., 0. }, { -17.806, -3.41 }, { 73.2776, 99.20074 }, { 101.857496, -444.634694 }, { -269.1458768, 388.7308864 } }; +}; + +DISABLE_CONVERSION_WARNING_END + +BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_INT, SystemInt) +{ + auto res = fratio::VietaAlgoi::polyCoeffFromRoot(data); + + for (size_t i = 0; i < res.size(); ++i) + BOOST_CHECK_EQUAL(res[i], results[i]); +} + +BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_FLOAT, SystemFloat) +{ + auto res = fratio::VietaAlgof::polyCoeffFromRoot(data); + + for (size_t i = 0; i < res.size(); ++i) + BOOST_CHECK_SMALL(std::abs(res[i] - results[i]), 1e-6f); +} + +BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_DOUBLE, SystemFloat) +{ + auto res = fratio::VietaAlgod::polyCoeffFromRoot(data); + + for (size_t i = 0; i < res.size(); ++i) + BOOST_CHECK_SMALL(std::abs(res[i] - results[i]), 1e-14); +} + +BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_CINT, SystemCInt) +{ + auto res = fratio::VietaAlgoci::polyCoeffFromRoot(data); + + for (size_t i = 0; i < res.size(); ++i) + BOOST_CHECK_EQUAL(res[i], results[i]); +} + +BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_CFLOAT, SystemCFloat) +{ + auto res = fratio::VietaAlgocf::polyCoeffFromRoot(data); + + for (size_t i = 0; i < res.size(); ++i) + BOOST_CHECK_SMALL(std::abs(res[i] - results[i]), 1e-4f); +} + +BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_CDOUBLE, SystemCFloat) +{ + auto res = fratio::VietaAlgocd::polyCoeffFromRoot(data); + + for (size_t i = 0; i < res.size(); ++i) + BOOST_CHECK_SMALL(std::abs(res[i] - results[i]), 1e-12); +} diff --git a/tests/warning_macro.h b/tests/warning_macro.h new file mode 100644 index 0000000..67d0e68 --- /dev/null +++ b/tests/warning_macro.h @@ -0,0 +1,23 @@ +#pragma once + +#ifdef _MSC_VER +#define DCW_BEGIN \ + __pragma(warning(push)) \ + __pragma(warning(disable : 4305)) +#define DCW_END __pragma(warning(pop)) +#else +#ifdef __GNUC__ || __clang__ +#define DCW_BEGIN \ + _Pragma("GCC warning push") \ + _Pragma("GCC diagnostic ignored \"-Wconversion\"") +#define DCW_END _Pragma("GCC warning pop") +#endif +#endif + +#ifdef DCW_BEGIN +#define DISABLE_CONVERSION_WARNING_BEGIN DCW_BEGIN +#define DISABLE_CONVERSION_WARNING_END DCW_END +#else +#define DISABLE_CONVERSION_WARNING_BEGIN +#define DISABLE_CONVERSION_WARNING_END +#endif