kopia lustrzana https://github.com/vsamy/DiFipp
Header-only lib.
Add digital filter. Add Butterworth filter. Add Moving average filter. Add polynomial transformation (compute polynome coeff from roots). Add test units.topic/diffentiators
rodzic
3c710edc1b
commit
7f350bdf53
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "GenericFilter.h"
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
namespace fratio {
|
||||
|
||||
// https://www.dsprelated.com/showarticle/1119.php
|
||||
template <typename T>
|
||||
class Butterworth : public GenericFilter<T> {
|
||||
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<std::complex<T>> m_poles;
|
||||
};
|
||||
|
||||
} // namespace fratio
|
||||
|
||||
#include "Butterworth.inl"
|
|
@ -0,0 +1,92 @@
|
|||
#include "polynome_functions.h"
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace fratio {
|
||||
|
||||
template <typename T>
|
||||
Butterworth<T>::Butterworth(Type type)
|
||||
: m_type(type)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Butterworth<T>::Butterworth(size_t order, T fc, T fs, Type type)
|
||||
: m_type(type)
|
||||
{
|
||||
initialize(order, fc, fs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Butterworth<T>::setFilterParameters(size_t order, T fc, T fs)
|
||||
{
|
||||
initialize(order, fc, fs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Butterworth<T>::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 <typename T>
|
||||
void Butterworth<T>::computeDigitalRep()
|
||||
{
|
||||
T pi = static_cast<T>(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<T> scalePole;
|
||||
for (size_t k = 1; k <= m_order; ++k) {
|
||||
scalePole = scaleFactor * std::complex<T>(-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<std::complex<T>> numPoles(m_order, std::complex<T>(-1));
|
||||
std::vector<std::complex<T>> a = VietaAlgo<std::complex<T>>::polyCoeffFromRoot(m_poles);
|
||||
std::vector<std::complex<T>> b = VietaAlgo<std::complex<T>>::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 <typename T>
|
||||
void Butterworth<T>::updateCoeffSize()
|
||||
{
|
||||
m_aCoeff.resize(m_order + 1);
|
||||
m_bCoeff.resize(m_order + 1);
|
||||
resetFilter();
|
||||
}
|
||||
|
||||
} // namespace fratio
|
|
@ -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)
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include "GenericFilter.h"
|
||||
|
||||
namespace fratio {
|
||||
|
||||
template <typename T>
|
||||
class DigitalFilter : public GenericFilter<T> {
|
||||
public:
|
||||
DigitalFilter() = default;
|
||||
DigitalFilter(const std::vector<T>& aCoeff, const std::vector<T>& bCoeff)
|
||||
: GenericFilter<T>(aCoeff, bCoeff)
|
||||
{
|
||||
}
|
||||
|
||||
void setCoeff(const std::vector<double>& aCoeff, const std::vector<double>& 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
|
|
@ -1,44 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "type_checks.h"
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
|
||||
namespace fratio {
|
||||
|
||||
class GenericFilter {
|
||||
template <typename T, typename = std::enable_if_t<std::is_floating_point<T>::value && !std::is_const<T>::value>>
|
||||
class GenericFilter;
|
||||
|
||||
template <typename T>
|
||||
class GenericFilter<T> {
|
||||
public:
|
||||
GenericFilter() = default;
|
||||
GenericFilter(size_t nData);
|
||||
GenericFilter(size_t nData, const std::vector<double>& aCoeff, const std::vector<double>& bCoeff);
|
||||
|
||||
void setNData(size_t nData);
|
||||
void setCoeff(const std::vector<double>& aCoeff, const std::vector<double>& bCoeff);
|
||||
void getCoeff(std::vector<double>& aCoeff, std::vector<double>& 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<double> filter(const std::vector<double>& data);
|
||||
T stepFilter(T data);
|
||||
std::vector<T> filter(const std::vector<T>& data);
|
||||
void resetFilter();
|
||||
|
||||
// Eigen::VectorXd results() const noexcept;
|
||||
// std::vector<double> results() const noexcept { return m_filteredData; }
|
||||
|
||||
private:
|
||||
void checkCoeffs(const std::vector<double>& aCoeff, const std::vector<double>& bCoeff);
|
||||
void normalize();
|
||||
void shiftData();
|
||||
void getCoeff(std::vector<T>& aCoeff, std::vector<T>& bCoeff) const noexcept;
|
||||
|
||||
protected:
|
||||
std::vector<double> m_aCoeff;
|
||||
std::vector<double> m_bCoeff;
|
||||
GenericFilter() = default;
|
||||
GenericFilter(const std::vector<T>& aCoeff, const std::vector<T>& bCoeff);
|
||||
virtual ~GenericFilter() = default;
|
||||
|
||||
std::vector<double> m_filteredData;
|
||||
std::vector<double> m_rawData;
|
||||
// Eigen::MatrixXd m_filteredData;
|
||||
// Eigen::MatrixXd m_rawData;
|
||||
void checkCoeff(const std::vector<T>& aCoeff, const std::vector<T>& bCoeff);
|
||||
void normalize();
|
||||
|
||||
protected:
|
||||
std::vector<T> m_aCoeff;
|
||||
std::vector<T> m_bCoeff;
|
||||
|
||||
private:
|
||||
std::vector<T> m_filteredData;
|
||||
std::vector<T> m_rawData;
|
||||
};
|
||||
|
||||
} // namespace fratio
|
||||
} // namespace fratio
|
||||
|
||||
#include "GenericFilter.inl"
|
|
@ -1,47 +1,9 @@
|
|||
#include "GenericFilter.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace fratio {
|
||||
|
||||
GenericFilter::GenericFilter(size_t nData)
|
||||
{
|
||||
}
|
||||
// Public functions
|
||||
|
||||
GenericFilter::GenericFilter(size_t nData, const std::vector<double>& aCoeff, const std::vector<double>& 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<double>& aCoeff, const std::vector<double>& bCoeff)
|
||||
{
|
||||
checkCoeffs(aCoeff, bCoeff);
|
||||
|
||||
m_aCoeff = aCoeff;
|
||||
m_bCoeff = bCoeff;
|
||||
resetFilter();
|
||||
|
||||
normalize();
|
||||
}
|
||||
|
||||
void GenericFilter::getCoeff(std::vector<double>& aCoeff, std::vector<double>& bCoeff) const noexcept
|
||||
{
|
||||
aCoeff = m_aCoeff;
|
||||
bCoeff = m_bCoeff;
|
||||
}
|
||||
|
||||
double GenericFilter::stepFilter(double data)
|
||||
template <typename T>
|
||||
T GenericFilter<T>::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<double> GenericFilter::filter(const std::vector<double>& data)
|
||||
template <typename T>
|
||||
std::vector<T> GenericFilter<T>::filter(const std::vector<T>& data)
|
||||
{
|
||||
std::vector<double> results;
|
||||
std::vector<T> results;
|
||||
results.reserve(data.size());
|
||||
|
||||
for (double d : data)
|
||||
for (T d : data)
|
||||
results.emplace_back(stepFilter(d));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void GenericFilter::resetFilter()
|
||||
template <typename T>
|
||||
void GenericFilter<T>::resetFilter()
|
||||
{
|
||||
m_filteredData.assign(m_aCoeff.size(), 0);
|
||||
m_rawData.assign(m_bCoeff.size(), 0);
|
||||
}
|
||||
|
||||
void GenericFilter::normalize()
|
||||
template <typename T>
|
||||
void GenericFilter<T>::getCoeff(std::vector<T>& aCoeff, std::vector<T>& 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 <typename T>
|
||||
GenericFilter<T>::GenericFilter(const std::vector<T>& aCoeff, const std::vector<T>& bCoeff)
|
||||
: m_aCoeff(aCoeff)
|
||||
, m_bCoeff(bCoeff)
|
||||
, m_filteredData(aCoeff.size(), 0)
|
||||
, m_rawData(bCoeff.size(), 0)
|
||||
{
|
||||
checkCoeff(aCoeff, bCoeff);
|
||||
normalize();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void GenericFilter<T>::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<double>& aCoeff, const std::vector<double>& bCoeff)
|
||||
template <typename T>
|
||||
void GenericFilter<T>::checkCoeff(const std::vector<T>& aCoeff, const std::vector<T>& bCoeff)
|
||||
{
|
||||
std::stringstream err;
|
||||
if (aCoeff.size() == 0)
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "GenericFilter.h"
|
||||
|
||||
namespace fratio {
|
||||
|
||||
template <typename T>
|
||||
class MovingAverage : public GenericFilter<T> {
|
||||
public:
|
||||
MovingAverage() = default;
|
||||
MovingAverage(size_t windowSize)
|
||||
: GenericFilter<T>({ T(1) }, std::vector<T>(windowSize, T(1) / windowSize))
|
||||
{
|
||||
}
|
||||
|
||||
void setWindowSize(size_t windowSize) { setCoeff({ T(1) }, std::vector<T>(windowSize, T(1) / windowSize)); }
|
||||
size_t windowSize() const noexcept { return m_bCoeff.size(); }
|
||||
};
|
||||
|
||||
} // namespace fratio
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "Butterworth.h"
|
||||
#include "DigitalFilter.h"
|
||||
#include "MovingAverage.h"
|
||||
#include "polynome_functions.h"
|
||||
|
||||
namespace fratio {
|
||||
|
||||
using DigitalFilterf = DigitalFilter<float>;
|
||||
using DigitalFilterd = DigitalFilter<double>;
|
||||
using MovingAveragef = MovingAverage<float>;
|
||||
using MovingAveraged = MovingAverage<double>;
|
||||
using Butterworthf = Butterworth<float>;
|
||||
using Butterworthd = Butterworth<double>;
|
||||
|
||||
using VietaAlgof = VietaAlgo<float>;
|
||||
using VietaAlgod = VietaAlgo<double>;
|
||||
using VietaAlgoi = VietaAlgo<int>;
|
||||
using VietaAlgocf = VietaAlgo<std::complex<float>>;
|
||||
using VietaAlgocd = VietaAlgo<std::complex<double>>;
|
||||
using VietaAlgoci = VietaAlgo<std::complex<int>>;
|
||||
|
||||
} // namespace fratio
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "type_checks.h"
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
namespace fratio {
|
||||
|
||||
template <typename T, typename = std::enable_if_t<std::is_arithmetic<T>::value || is_complex_t<T>::value>>
|
||||
struct VietaAlgo;
|
||||
|
||||
template <typename T>
|
||||
struct VietaAlgo<T> {
|
||||
// Vieta's computation: https://en.wikipedia.org/wiki/Vieta%27s_formulas
|
||||
static std::vector<T> polyCoeffFromRoot(const std::vector<T>& poles);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> VietaAlgo<T>::polyCoeffFromRoot(const std::vector<T>& poles)
|
||||
{
|
||||
std::vector<T> 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
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <complex>
|
||||
|
||||
namespace fratio {
|
||||
|
||||
template <typename T>
|
||||
struct is_complex_t : public std::false_type {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_complex_t<std::complex<T>> : public std::true_type {
|
||||
};
|
||||
|
||||
} // namespace fratio
|
|
@ -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)
|
|
@ -0,0 +1,76 @@
|
|||
#define BOOST_TEST_MODULE ButterworthFilterTests
|
||||
|
||||
#include "fratio.h"
|
||||
#include "warning_macro.h"
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
DISABLE_CONVERSION_WARNING_BEGIN
|
||||
|
||||
template <typename T>
|
||||
struct System {
|
||||
std::vector<T> data = { 1., 2., 3., 4., 5., 6., 7., 8. };
|
||||
size_t order = 5;
|
||||
T fc = 10;
|
||||
T fs = 100;
|
||||
std::vector<T> aCoeffRes = { 1.000000000000000, -2.975422109745684, 3.806018119320413, -2.545252868330468, 0.881130075437837, -0.125430622155356 };
|
||||
std::vector<T> bCoeffRes = { 0.001282581078961, 0.006412905394803, 0.012825810789607, 0.012825810789607, 0.006412905394803, 0.001282581078961 };
|
||||
std::vector<T> 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<float>)
|
||||
{
|
||||
auto gf = fratio::Butterworthf(order, fc, fs);
|
||||
|
||||
std::vector<float> 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<double>)
|
||||
{
|
||||
auto gf = fratio::Butterworthd(order, fc, fs);
|
||||
|
||||
std::vector<double> 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);
|
||||
}
|
|
@ -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)
|
||||
addTest(ButterWorthFilterTests)
|
||||
addTest(GenericFilterTests)
|
||||
addTest(DigitalFilterTests)
|
||||
addTest(MovingAverageFilterTests)
|
||||
addTest(polynome_functions_tests)
|
|
@ -0,0 +1,57 @@
|
|||
#define BOOST_TEST_MODULE DigitalFilterTests
|
||||
|
||||
#include "fratio.h"
|
||||
#include "warning_macro.h"
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
DISABLE_CONVERSION_WARNING_BEGIN
|
||||
|
||||
template <typename T>
|
||||
struct System {
|
||||
std::vector<T> data = { 1., 2., 3., 4. };
|
||||
std::vector<T> aCoeff = { 1., -0.99993717 };
|
||||
std::vector<T> bCoeff = { 0.99996859, -0.99996859 };
|
||||
std::vector<T> results = { 0.99996859, 1.999874351973491, 2.999717289867956, 3.999497407630634 };
|
||||
};
|
||||
|
||||
DISABLE_CONVERSION_WARNING_END
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(DIGITAL_FILTER_FLOAT, System<float>)
|
||||
{
|
||||
auto gf = fratio::DigitalFilterf(aCoeff, bCoeff);
|
||||
BOOST_REQUIRE_EQUAL(aCoeff.size(), gf.aOrder());
|
||||
BOOST_REQUIRE_EQUAL(bCoeff.size(), gf.bOrder());
|
||||
|
||||
std::vector<float> 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<double>)
|
||||
{
|
||||
auto gf = fratio::DigitalFilterd(aCoeff, bCoeff);
|
||||
BOOST_REQUIRE_EQUAL(aCoeff.size(), gf.aOrder());
|
||||
BOOST_REQUIRE_EQUAL(bCoeff.size(), gf.bOrder());
|
||||
|
||||
std::vector<double> 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);
|
||||
}
|
|
@ -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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define BOOST_TEST_MODULE FilterTest
|
||||
|
||||
#include "GenericFilter.h"
|
||||
#include <Eigen/Core>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
struct System1 {
|
||||
std::vector<double> data = { 1., 2., 3., 4. };
|
||||
std::vector<double> aCoeff = { 1., -0.99993717 };
|
||||
std::vector<double> bCoeff = { 0.99996859, -0.99996859 };
|
||||
std::vector<double> 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<double> 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);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#define BOOST_TEST_MODULE GenericFilterTests
|
||||
|
||||
#include "fratio.h"
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
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);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
#define BOOST_TEST_MODULE MovingAverageFilterTests
|
||||
|
||||
#include "fratio.h"
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
template <typename T>
|
||||
struct System {
|
||||
std::vector<T> data = { 1., 2., 3., 4., 5., 6. };
|
||||
size_t windowSize = 4;
|
||||
std::vector<T> results = { 0.25, 0.75, 1.5, 2.5, 3.5, 4.5 };
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(MOVING_AVERAGE_FLOAT, System<float>)
|
||||
{
|
||||
auto ma = fratio::MovingAveragef(windowSize);
|
||||
std::vector<float> 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<double>)
|
||||
{
|
||||
auto ma = fratio::MovingAveraged(windowSize);
|
||||
std::vector<double> 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);
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
#define BOOST_TEST_MODULE polynome_functions_tests
|
||||
|
||||
#include "fratio.h"
|
||||
#include "warning_macro.h"
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
struct SystemInt {
|
||||
std::vector<int> data = { 1, 1, 1, 1 };
|
||||
std::vector<int> results = { 1, -4, 6, -4, 1 };
|
||||
};
|
||||
|
||||
struct SystemCInt {
|
||||
std::vector<std::complex<int>> data = { { 1, 1 }, { -1, 4 }, { 12, -3 }, { 5, 2 } };
|
||||
std::vector<std::complex<int>> results = { { 1, 0 }, { -17, -4 }, { 66, 97 }, { 127, -386 }, { -357, 153 } };
|
||||
};
|
||||
|
||||
DISABLE_CONVERSION_WARNING_BEGIN
|
||||
|
||||
template <typename T>
|
||||
struct SystemFloat {
|
||||
std::vector<T> data = { 0.32, -0.0518, 41.4, 0.89 };
|
||||
std::vector<T> results = { 1., -42.558199999999999, 48.171601999999993, -9.181098159999999, -0.610759296 };
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct SystemCFloat {
|
||||
std::vector<std::complex<T>> data = { { 1.35, 0.2 }, { -1.5, 4.45 }, { 12.8, -3.36 }, { 5.156, 2.12 } };
|
||||
std::vector<std::complex<T>> 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<float>)
|
||||
{
|
||||
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<double>)
|
||||
{
|
||||
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<float>)
|
||||
{
|
||||
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<double>)
|
||||
{
|
||||
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);
|
||||
}
|
|
@ -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
|
Ładowanie…
Reference in New Issue