Porównaj commity

...

24 Commity

Autor SHA1 Wiadomość Data
Vincent Samy a38c1e75a2 Update CMake and tests. 2021-03-19 13:45:58 +09:00
vincent samy ae6acd85e1 Merge branch 'topic/cross-compile'
Closes #1
Closes #2
2020-03-30 18:21:14 +09:00
vincent samy 027d4b49e8 c++17 needed for MSVC.
Test on MSVC 16.5.1.
2020-03-30 18:11:30 +09:00
vincent samy 2e14f70684 c++17 needed for MSVC.
Test on MSVC 16.5.1.
2020-03-30 18:08:08 +09:00
Vincent Samy 6a11b84fbc Set to c++14. 2020-03-30 18:00:43 +09:00
Vincent Samy 213be7549b Build and test pass for GCC 7.5.0 2020-03-30 17:23:23 +09:00
Vincent Samy e6e370642e Build and test pass with clang 6.0.0. 2020-03-30 17:15:34 +09:00
Vincent Samy b12580b323 fix name typo. 2019-12-10 17:54:19 +09:00
Vincent Samy 2cd67a45b4 Uniformization of names. 2019-11-12 10:12:23 +09:00
Vincent Samy 050dd14760 Forgot cmake and includes. 2019-11-08 18:02:48 +09:00
Vincent Samy 92e745712a Update reference. 2019-11-08 17:52:39 +09:00
Vincent Samy 3251815ada All centered filter test passes.
Though time-varying second order is not working?
May need deeper tests.
2019-11-08 17:46:43 +09:00
Vincent Samy d049e600d0 Time-fixed central filter pass. 2019-11-08 15:52:27 +09:00
vincent samy 934a0ed17f Updates... 2019-11-06 09:16:00 +09:00
vincent samy 544bead65c Pass compilation 2019-11-01 18:04:02 +09:00
vincent samy d08dfd9971 Passing previous test using catch. 2019-11-01 14:21:19 +09:00
vincent samy b90e14801b Merge branch 'topic/diffentiators' of https://github.com/vsamy/DiFipp into topic/diffentiators 2019-10-29 17:55:46 +09:00
Vincent Samy a2c073ae3d Pass to catch for tests. 2019-10-29 17:55:25 +09:00
Vincent Samy a7b8bdb8fa Fix naming. 2019-10-29 16:34:45 +09:00
vincent samy 19439a01a3 Fix naming. 2019-10-29 15:58:37 +09:00
Vincent Samy 60c0b5c11f Fix some stuffs. 2019-10-29 15:03:13 +09:00
Vincent Samy 1242b85c67 Upgrade cmake modules. 2019-10-29 13:40:46 +09:00
Vincent Samy 09a697bb5c Change Base filter class. 2019-10-29 13:27:48 +09:00
Vincent Samy 3b927b8174 Add differentiators. 2019-10-29 13:27:15 +09:00
30 zmienionych plików z 8144 dodań i 406 usunięć

Wyświetl plik

@ -13,11 +13,12 @@ AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine AlwaysBreakTemplateDeclarations: No
BinPackArguments: true BinPackArguments: true
BinPackParameters: true BinPackParameters: true
BraceWrapping: BraceWrapping:

Wyświetl plik

@ -26,19 +26,18 @@
# either expressed or implied, of the FreeBSD Project. # either expressed or implied, of the FreeBSD Project.
# Version minimum # Version minimum
cmake_minimum_required(VERSION 3.1.3) cmake_minimum_required(VERSION 3.8.2)
set(PROJECT_NAME diFi++) set(PROJECT_NAME difi++)
set(PROJECT_DESCRIPTION "Filter using rational transfer function") set(PROJECT_DESCRIPTION "Filter using rational transfer function")
set(PROJECT_URL "https://github.com/vsamy/DiFipp") set(PROJECT_URL "https://github.com/vsamy/DiFipp")
SET(PROJECT_DEBUG_POSTFIX "_d") SET(PROJECT_DEBUG_POSTFIX "_d")
set(INSTALL_GENERATED_HEADERS OFF) set(INSTALL_GENERATED_HEADERS OFF)
set(DOXYGEN_USE_MATHJAX "NO") set(DOXYGEN_USE_MATHJAX "NO")
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 17)
option(BUILD_TESTING "Disable unit tests." ON) option(BUILD_TESTING "Disable unit tests." ON)
option(BUILD_TEST_STATIC_BOOST "Build unit tests with static boost libraries" OFF)
option(THROW_ON_CONTRACT_VIOLATION "Throw an error when program fails." ON) option(THROW_ON_CONTRACT_VIOLATION "Throw an error when program fails." ON)
option(TERMINATE_ON_CONTRACT_VIOLATION "Terminate program when an error occurs. (Default)" OFF) option(TERMINATE_ON_CONTRACT_VIOLATION "Terminate program when an error occurs. (Default)" OFF)
option(UNENFORCED_ON_CONTRACT_VIOLATION "Do not perform any check." OFF) option(UNENFORCED_ON_CONTRACT_VIOLATION "Do not perform any check." OFF)
@ -71,6 +70,3 @@ add_subdirectory(include)
if(${BUILD_TESTING}) if(${BUILD_TESTING})
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()
setup_project_finalize()
setup_project_package_finalize()

Wyświetl plik

@ -7,10 +7,16 @@ DiFi++ is a small c++ header-only library for **DI**gital **FI**lters based on
The implementation is based on well written article from Neil Robertson. The implementation is based on well written article from Neil Robertson.
Please check out the followings Please check out the followings
* https://www.dsprelated.com/showarticle/1119.php
* https://www.dsprelated.com/showarticle/1135.php * [Butterworth filter](https://www.dsprelated.com/showarticle/1119.php)
* https://www.dsprelated.com/showarticle/1128.php
* https://www.dsprelated.com/showarticle/1131.php * [Highpass filters](https://www.dsprelated.com/showarticle/1135.php)
* [Bandpass filters](https://www.dsprelated.com/showarticle/1128.php)
* [Band-reject filters](https://www.dsprelated.com/showarticle/1131.php)
and the differentiators from [Pavel Holoborodko](http://www.holoborodko.com/pavel/)
The library has been tested against Matlab results. The library has been tested against Matlab results.
@ -32,4 +38,5 @@ make install
Note Note
----- -----
The method used is close but somewhat different from Matlab methods and Butterworth band-reject has quite different results (precision of 1e-8). The method used is close but somewhat different from Matlab methods and Butterworth band-reject has quite different results (precision of 1e-8).

2
cmake

@ -1 +1 @@
Subproject commit 192812cc2c5d8aaffbc8536d0b0a2a7c11dad53b Subproject commit f4997a81cebfa2dfb69733bc088c55688965dfe8

Wyświetl plik

@ -0,0 +1,137 @@
// Copyright (c) 2019, Vincent SAMY
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
#pragma once
#include "gsl/gsl_assert.h"
#include "type_checks.h"
#include "typedefs.h"
#include <string>
namespace difi {
// TODO: noexcept(Function of gsl variable)
// TODO: constructor with universal refs
// TODO: setCoeffs with universal refs
/*! \brief Low-level filter.
*
* It creates the basic and common functions of all linear filter that can written as a digital filter.
* This class can not be instantiated directly.
*
* \warning In Debug mode, all functions may throw if a filter is badly initialized.
* This not the case in Realese mode.
*
* \tparam T Floating type.
*/
template <typename T, typename Derived>
class BaseFilter {
static_assert(std::is_floating_point<T>::value && !std::is_const<T>::value, "Only accept non-complex floating point types.");
friend Derived;
public:
/*! \brief Reset the data and filtered data. */
void resetFilter() noexcept { derived().resetFilter(); };
/*!< \brief Return the filter type */
FilterType type() const noexcept { return m_type; }
/*! \brief Get how far back is the filtered value.
* The filtered value is computed at time \f$tc=t - center()*\Delta T\f$ where \f$t\f$ is the current time
* \f$\Delta T\f$ is your sampling time.
* \note For FilterType::Backward filter, the function returns 0.
*/
Eigen::Index center() const noexcept { return (m_type == FilterType::Backward ? 0 : ((m_bCoeff.size() - 1) / 2)); }
/*! \brief Get digital filter coefficients.
*
* It will automatically resize the given vectors.
* \param[out] aCoeff Denominator coefficients of the filter in decreasing order.
* \param[out] bCoeff Numerator coefficients of the filter in decreasing order.
*/
void getCoeffs(vectX_t<T>& aCoeff, vectX_t<T>& bCoeff) const noexcept;
/*! \brief Return coefficients of the denominator polynome. */
const vectX_t<T>& aCoeff() const noexcept { return m_aCoeff; }
/*! \brief Return coefficients of the numerator polynome. */
const vectX_t<T>& bCoeff() const noexcept { return m_bCoeff; }
/*! \brief Return the order the denominator polynome order of the filter. */
Eigen::Index aOrder() const noexcept { return m_aCoeff.size(); }
/*! \brief Return the order the numerator polynome order of the filter. */
Eigen::Index bOrder() const noexcept { return m_bCoeff.size(); }
/*! \brief Return the initialization state of the filter0 */
bool isInitialized() const noexcept { return m_isInitialized; }
/*! \brief Set type of filter (one-sided or centered)
*
* \param type The filter type.
* \warning bCoeff must be set before.
*/
void setType(FilterType type);
/*! \brief Set the new coefficients of the filters.
*
* It awaits a universal reference.
* \param aCoeff Denominator coefficients of the filter in decreasing order.
* \param bCoeff Numerator coefficients of the filter in decreasing order.
*/
void setCoeffs(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff);
protected:
/*! \brief Normalized the filter coefficients such that aCoeff(0) = 1. */
void normalizeCoeffs();
/*! \brief Check for bad coefficients.
*
* Set the filter status to ready is everything is fine.
* \param aCoeff Denominator coefficients of the filter.
* \param bCoeff Numerator coefficients of the filter.
* \return True if the filter status is set on READY.
*/
bool checkCoeffs(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff);
private:
/*! \brief Default uninitialized constructor. */
BaseFilter() = default;
/*! \brief Constructor.
* \param aCoeff Denominator coefficients of the filter in decreasing order.
* \param bCoeff Numerator coefficients of the filter in decreasing order.
* \param type Type of the filter.
*/
BaseFilter(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, FilterType type = FilterType::Backward);
/*! \brief Default destructor. */
virtual ~BaseFilter() = default;
Derived& derived() noexcept { return *static_cast<Derived*>(this); }
const Derived& derived() const noexcept { return *static_cast<const Derived*>(this); }
private:
FilterType m_type; /*!< Type of filter. Default is FilterType::Backward. */
bool m_isInitialized = false; /*!< Initialization state of the filter. Default is false */
vectX_t<T> m_aCoeff; /*!< Denominator coefficients of the filter */
vectX_t<T> m_bCoeff; /*!< Numerator coefficients of the filter */
vectX_t<T> m_filteredData; /*!< Last set of filtered data */
vectX_t<T> m_rawData; /*!< Last set of non-filtered data */
};
} // namespace difi
#include "BaseFilter.tpp"

Wyświetl plik

@ -0,0 +1,93 @@
// Copyright (c) 2019, Vincent SAMY
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
#include <limits>
namespace difi {
// Public functions
template <typename T, typename Derived>
void BaseFilter<T, Derived>::setType(FilterType type)
{
Expects(type == FilterType::Centered ? m_bCoeff.size() > 2 && m_bCoeff.size() % 2 == 1 : true);
m_type = type;
}
template <typename T, typename Derived>
void BaseFilter<T, Derived>::setCoeffs(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff)
{
Expects(checkCoeffs(aCoeff, bCoeff));
m_aCoeff = aCoeff;
m_bCoeff = bCoeff;
normalizeCoeffs();
resetFilter();
m_isInitialized = true;
}
template <typename T, typename Derived>
void BaseFilter<T, Derived>::getCoeffs(vectX_t<T>& aCoeff, vectX_t<T>& bCoeff) const noexcept
{
aCoeff = m_aCoeff;
bCoeff = m_bCoeff;
}
// Protected functions
template <typename T, typename Derived>
BaseFilter<T, Derived>::BaseFilter(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, FilterType type)
: m_type(type)
, m_aCoeff(aCoeff)
, m_bCoeff(bCoeff)
, m_filteredData(aCoeff.size())
, m_rawData(bCoeff.size())
{
Expects(checkCoeffs(aCoeff, bCoeff));
normalizeCoeffs();
resetFilter();
m_isInitialized = true;
}
template <typename T, typename Derived>
void BaseFilter<T, Derived>::normalizeCoeffs()
{
T a0 = m_aCoeff(0);
if (std::abs(a0 - T(1)) < std::numeric_limits<T>::epsilon())
return;
m_aCoeff /= a0;
m_bCoeff /= a0;
}
template <typename T, typename Derived>
bool BaseFilter<T, Derived>::checkCoeffs(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff)
{
bool centering = (m_type == FilterType::Centered ? (bCoeff.size() % 2 == 1) : true);
return aCoeff.size() > 0 && std::abs(aCoeff[0]) > std::numeric_limits<T>::epsilon() && bCoeff.size() > 0 && centering;
}
} // namespace difi

Wyświetl plik

@ -60,7 +60,7 @@ public:
/*! \brief Function to help you design a Butterworth filter. /*! \brief Function to help you design a Butterworth filter.
* *
* It finds optimal values of the order and cut-off frequency. * It finds optimal values of the order and cut-off frequency.
* \warning Works only for low-pass and high-pass filters. * \note Works only for low-pass and high-pass filters.
* \see http://www.matheonics.com/Tutorials/Butterworth.html#Paragraph_3.2 * \see http://www.matheonics.com/Tutorials/Butterworth.html#Paragraph_3.2
* \see https://www.mathworks.com/help/signal/ref/buttord.html#d120e11079 * \see https://www.mathworks.com/help/signal/ref/buttord.html#d120e11079
* \param wPass Pass band edge. * \param wPass Pass band edge.

Wyświetl plik

@ -131,7 +131,7 @@ void Butterworth<T>::computeDigitalRep(T fc)
} }
scaleAmplitude(aCoeff, bCoeff); scaleAmplitude(aCoeff, bCoeff);
setCoeffs(std::move(aCoeff), std::move(bCoeff)); this->setCoeffs(std::move(aCoeff), std::move(bCoeff));
} }
template <typename T> template <typename T>
@ -164,14 +164,14 @@ void Butterworth<T>::computeBandDigitalRep(T fLower, T fUpper)
else else
scaleAmplitude(aCoeff, bCoeff); scaleAmplitude(aCoeff, bCoeff);
setCoeffs(std::move(aCoeff), std::move(bCoeff)); this->setCoeffs(std::move(aCoeff), std::move(bCoeff));
} }
template <typename T> template <typename T>
std::complex<T> Butterworth<T>::generateAnalogPole(int k, T fpw1) std::complex<T> Butterworth<T>::generateAnalogPole(int k, T fpw1)
{ {
auto thetaK = [pi = pi<T>, order = m_order](int k) -> T { auto thetaK = [pi = pi<T>, order = m_order](int k) -> T {
return (2 * k - 1) * pi / (2 * order); return static_cast<float>(2 * k - 1) * pi / static_cast<float>(2 * order);
}; };
std::complex<T> analogPole(-std::sin(thetaK(k)), std::cos(thetaK(k))); std::complex<T> analogPole(-std::sin(thetaK(k)), std::cos(thetaK(k)));
@ -189,7 +189,7 @@ template <typename T>
std::pair<std::complex<T>, std::complex<T>> Butterworth<T>::generateBandAnalogPole(int k, T fpw0, T bw) std::pair<std::complex<T>, std::complex<T>> Butterworth<T>::generateBandAnalogPole(int k, T fpw0, T bw)
{ {
auto thetaK = [pi = pi<T>, order = m_order](int k) -> T { auto thetaK = [pi = pi<T>, order = m_order](int k) -> T {
return (2 * k - 1) * pi / (2 * order); return static_cast<float>(2 * k - 1) * pi / static_cast<float>(2 * order);
}; };
std::complex<T> analogPole(-std::sin(thetaK(k)), std::cos(thetaK(k))); std::complex<T> analogPole(-std::sin(thetaK(k)), std::cos(thetaK(k)));

Wyświetl plik

@ -26,13 +26,17 @@
# either expressed or implied, of the FreeBSD Project. # either expressed or implied, of the FreeBSD Project.
set(HEADERS set(HEADERS
BaseFilter.h
BaseFilter.tpp
BilinearTransform.h BilinearTransform.h
Butterworth.h Butterworth.h
Butterworth.tpp Butterworth.tpp
differentiators.h
difi difi
DigitalFilter.h DigitalFilter.h
GenericFilter.h GenericFilter.h
GenericFilter.tpp GenericFilter.tpp
math_utils.h
MovingAverage.h MovingAverage.h
polynome_functions.h polynome_functions.h
type_checks.h type_checks.h
@ -43,7 +47,7 @@ set(GSL_HEADERS gsl/gsl_assert.h)
add_library(${PROJECT_NAME} INTERFACE) add_library(${PROJECT_NAME} INTERFACE)
target_include_directories(${PROJECT_NAME} INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<INSTALL_INTERFACE:include>) target_include_directories(${PROJECT_NAME} INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<INSTALL_INTERFACE:include>)
target_include_directories(${PROJECT_NAME} SYSTEM INTERFACE "${EIGEN3_INCLUDE_DIR}") target_include_directories(${PROJECT_NAME} SYSTEM INTERFACE "${EIGEN3_INCLUDE_DIR}")
install(TARGETS ${PROJECT_NAME} install(TARGETS ${PROJECT_NAME}
EXPORT "${TARGETS_EXPORT_NAME}" EXPORT "${TARGETS_EXPORT_NAME}"
RUNTIME DESTINATION bin RUNTIME DESTINATION bin

Wyświetl plik

@ -45,40 +45,12 @@ public:
/*! \brief Constructor. /*! \brief Constructor.
* \param aCoeff Denominator coefficients of the filter in decreasing order. * \param aCoeff Denominator coefficients of the filter in decreasing order.
* \param bCoeff Numerator coefficients of the filter in decreasing order. * \param bCoeff Numerator coefficients of the filter in decreasing order.
* \param type Type of the filter.
*/ */
DigitalFilter(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff) DigitalFilter(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, FilterType type = FilterType::Backward)
: GenericFilter<T>(aCoeff, bCoeff) : GenericFilter<T>(aCoeff, bCoeff, type)
{ {
} }
void setCoefficients(vectX_t<T>&& aCoeff, vectX_t<T>&& bCoeff)
{
setCoeffs(std::forward(aCoeff), std::forward(bCoeff));
}
};
/*! \brief Basic centered digital filter.
*
* This filter allows you to set any centered digital filter based on its coefficients.
* \tparam T Floating type.
*/
template <typename T>
class CenteredDigitalFilter : public GenericFilter<T> {
public:
/*! \brief Default uninitialized constructor. */
CenteredDigitalFilter() = default;
/*! \brief Constructor.
* \param aCoeff Denominator coefficients of the filter in decreasing order.
* \param bCoeff Numerator coefficients of the filter in decreasing order.
*/
CenteredDigitalFilter(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff)
: GenericFilter<T>(aCoeff, bCoeff, Type::Centered)
{
}
void setCoefficients(vectX_t<T>&& aCoeff, vectX_t<T>&& bCoeff)
{
setCoeffs(std::forward(aCoeff), std::forward(bCoeff));
setType(Type::Centered);
}
}; };
} // namespace difi } // namespace difi

Wyświetl plik

@ -27,36 +27,18 @@
#pragma once #pragma once
#include "gsl/gsl_assert.h" #include "BaseFilter.h"
#include "type_checks.h"
#include "typedefs.h"
#include <stddef.h>
#include <string>
namespace difi { namespace difi {
// TODO: noexcept(Function of gsl variable)
// TODO: constructor with universal refs
/*! \brief Low-level filter.
*
* It creates the basic and common functions of all linear filter that can written as a digital filter.
* This class can not be instantiated directly.
*
* \warning In Debug mode, all functions may throw if a filter is badly initialized.
* This not the case in Realese mode.
*
* \tparam T Floating type.
*/
template <typename T> template <typename T>
class GenericFilter { class GenericFilter : public BaseFilter<T, GenericFilter<T>> {
static_assert(std::is_floating_point<T>::value && !std::is_const<T>::value, "Only accept non-complex floating point types."); using Base = BaseFilter<T, GenericFilter<T>>;
using Base::m_isInitialized;
public: using Base::m_aCoeff;
enum class Type { using Base::m_bCoeff;
OneSided, using Base::m_rawData;
Centered using Base::m_filteredData;
};
public: public:
/*! \brief Filter a new data. /*! \brief Filter a new data.
@ -73,75 +55,59 @@ public:
* \return Filtered signal. * \return Filtered signal.
*/ */
vectX_t<T> filter(const vectX_t<T>& data); vectX_t<T> filter(const vectX_t<T>& data);
/*! \brief Reset the data and filtered data. */
void resetFilter() noexcept; void resetFilter() noexcept;
/*!< \brief Return the filter type */ protected:
Type type() const noexcept { return (m_center == 0 ? Type::OneSided : Type::Centered); } GenericFilter() = default;
/*! \brief Get digital filter coefficients. GenericFilter(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, FilterType type = FilterType::Backward)
: Base(aCoeff, bCoeff, type)
{}
};
template <typename T>
class TVGenericFilter : public BaseFilter<T, TVGenericFilter<T>> {
using Base = BaseFilter<T, TVGenericFilter<T>>;
using Base::m_isInitialized;
using Base::m_aCoeff;
using Base::m_bCoeff;
using Base::m_rawData;
using Base::m_filteredData;
public:
/*! \brief Filter a new data.
* *
* It will automatically resize the given vectors. * This function is practical for online application that does not know the whole signal in advance.
* \param[out] aCoeff Denominator coefficients of the filter in decreasing order. * \param data New data to filter.
* \param[out] bCoeff Numerator coefficients of the filter in decreasing order. * \return Filtered data.
*/ */
void getCoeffs(vectX_t<T>& aCoeff, vectX_t<T>& bCoeff) const noexcept; T stepFilter(const T& time, const T& data);
/*! \brief Return coefficients of the denominator polynome. */ /*! \brief Filter a signal.
const vectX_t<T>& aCoeff() const noexcept { return m_aCoeff; } *
/*! \brief Return coefficients of the numerator polynome. */ * Filter all data given by the signal.
const vectX_t<T>& bCoeff() const noexcept { return m_bCoeff; } * \param data Signal.
/*! \brief Return the order the denominator polynome order of the filter. */ * \return Filtered signal.
Eigen::Index aOrder() const noexcept { return m_aCoeff.size(); } */
/*! \brief Return the order the numerator polynome order of the filter. */ vectX_t<T> filter(const vectX_t<T>& data, const vectX_t<T>& time);
Eigen::Index bOrder() const noexcept { return m_bCoeff.size(); }
/*! \brief Return the initialization state of the filter0 */ void resetFilter() noexcept;
bool isInitialized() const noexcept { return m_isInitialized; }
protected: protected:
/*! \brief Default uninitialized constructor. */ TVGenericFilter() = default;
GenericFilter() = default; TVGenericFilter(size_t differentialOrder, const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, FilterType type = FilterType::Backward)
/*! \brief Constructor. : Base()
* \param aCoeff Denominator coefficients of the filter in decreasing order. , m_diffOrder(differentialOrder)
* \param bCoeff Numerator coefficients of the filter in decreasing order. {
* \param center Expects(differentialOrder >= 1);
*/ this->setCoeffs(aCoeff, bCoeff);
GenericFilter(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, Type type = Type::OneSided); this->setType(type);
/*! \brief Default destructor. */ }
virtual ~GenericFilter() = default;
/*! \brief Set type of filter (one-sided or centered)
*
* \param type The filter type.
* \warning bCoeff must be set before.
*/
void setType(Type type);
/*! \brief Set the new coefficients of the filters.
*
* It awaits a universal reference.
* \param aCoeff Denominator coefficients of the filter in decreasing order.
* \param bCoeff Numerator coefficients of the filter in decreasing order.
*/
template <typename T2>
void setCoeffs(T2&& aCoeff, T2&& bCoeff);
/*! \brief Normalized the filter coefficients such that aCoeff(0) = 1. */
void normalizeCoeffs();
/*! \brief Check for bad coefficients.
*
* Set the filter status to ready is everything is fine.
* \param aCoeff Denominator coefficients of the filter.
* \param bCoeff Numerator coefficients of the filter.
* \return True if the filter status is set on READY.
*/
bool checkCoeffs(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, Type type);
private: private:
Eigen::Index m_center = 0; /*!< Center of the filter. 0 is a one-sided filter. Default is 0. */ size_t m_diffOrder = 1;
bool m_isInitialized = false; /*!< Initialization state of the filter. Default is false */ vectX_t<T> m_timers;
vectX_t<T> m_aCoeff; /*!< Denominator coefficients of the filter */
vectX_t<T> m_bCoeff; /*!< Numerator coefficients of the filter */
vectX_t<T> m_filteredData; /*!< Last set of filtered data */
vectX_t<T> m_rawData; /*!< Last set of non-filtered data */
}; };
} // namespace difi } // namespace difi
#include "GenericFilter.tpp" #include "GenericFilter.tpp"

Wyświetl plik

@ -25,12 +25,8 @@
// of the authors and should not be interpreted as representing official policies, // of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project. // either expressed or implied, of the FreeBSD Project.
#include <limits>
namespace difi { namespace difi {
// Public functions
template <typename T> template <typename T>
T GenericFilter<T>::stepFilter(const T& data) T GenericFilter<T>::stepFilter(const T& data)
{ {
@ -42,10 +38,10 @@ T GenericFilter<T>::stepFilter(const T& data)
for (Eigen::Index i = m_filteredData.size() - 1; i > 0; --i) for (Eigen::Index i = m_filteredData.size() - 1; i > 0; --i)
m_filteredData(i) = m_filteredData(i - 1); m_filteredData(i) = m_filteredData(i - 1);
m_rawData[0] = data; m_rawData(0) = data;
m_filteredData[0] = 0; m_filteredData(0) = 0;
m_filteredData[m_center] = m_bCoeff.dot(m_rawData) - m_aCoeff.dot(m_filteredData); m_filteredData(0) = m_bCoeff.dot(m_rawData) - m_aCoeff.dot(m_filteredData);
return m_filteredData[m_center]; return m_filteredData(0);
} }
template <typename T> template <typename T>
@ -66,65 +62,50 @@ void GenericFilter<T>::resetFilter() noexcept
} }
template <typename T> template <typename T>
void GenericFilter<T>::setType(Type type) T TVGenericFilter<T>::stepFilter(const T& time, const T& data)
{ {
Expects(type == Type::Centered ? m_bCoeff.size() > 2 && m_bCoeff.size() % 2 == 1 : true); Expects(m_isInitialized);
m_center = (type == Type::OneSided ? 0 : (m_bCoeff.size() - 1) / 2);
// Slide data (can't use SIMD, but should be small)
for (Eigen::Index i = m_rawData.size() - 1; i > 0; --i) {
m_rawData(i) = m_rawData(i - 1);
m_timers(i) = m_timers(i - 1);
}
for (Eigen::Index i = m_filteredData.size() - 1; i > 0; --i)
m_filteredData(i) = m_filteredData(i - 1);
m_timers(0) = time;
const Eigen::Index M = (m_rawData.size() - 1) / 2;
auto bCoeff = m_bCoeff;
for (Eigen::Index i = 1; i < M + 1; ++i) {
const T diff = std::pow(m_timers(M - i) - m_timers(M + i), m_diffOrder);
bCoeff(M + i) /= diff;
bCoeff(M - i) /= diff;
bCoeff(M) -= (bCoeff(M - i) + bCoeff(M + i));
}
m_rawData(0) = data;
m_filteredData(0) = 0;
m_filteredData(0) = bCoeff.dot(m_rawData) - m_aCoeff.dot(m_filteredData);
return m_filteredData(0);
} }
template <typename T> template <typename T>
template <typename T2> vectX_t<T> TVGenericFilter<T>::filter(const vectX_t<T>& data, const vectX_t<T>& time)
void GenericFilter<T>::setCoeffs(T2&& aCoeff, T2&& bCoeff)
{ {
static_assert(std::is_convertible_v<T2, vectX_t<T>>, "The coefficients types should be convertible to vectX_t<T>"); Expects(m_isInitialized);
Expects(data.size() == time.size());
Expects(checkCoeffs(aCoeff, bCoeff, (m_center == 0 ? Type::OneSided : Type::Centered))); vectX_t<T> results(data.size());
m_aCoeff = aCoeff; for (Eigen::Index i = 0; i < data.size(); ++i)
m_bCoeff = bCoeff; results(i) = stepFilter(time(i), data(i));
normalizeCoeffs(); return results;
resetFilter();
m_isInitialized = true;
} }
template <typename T> template <typename T>
void GenericFilter<T>::getCoeffs(vectX_t<T>& aCoeff, vectX_t<T>& bCoeff) const noexcept void TVGenericFilter<T>::resetFilter() noexcept
{ {
aCoeff = m_aCoeff; m_filteredData.setZero(m_aCoeff.size());
bCoeff = m_bCoeff; m_rawData.setZero(m_bCoeff.size());
} m_timers.setZero(m_bCoeff.size());
// Protected functions
template <typename T>
GenericFilter<T>::GenericFilter(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, Type type)
: m_aCoeff(aCoeff)
, m_bCoeff(bCoeff)
, m_filteredData(aCoeff.size())
, m_rawData(bCoeff.size())
{
Expects(checkCoeffs(aCoeff, bCoeff, type));
m_center = (type == Type::OneSided ? 0 : (bCoeff.size() - 1) / 2);
normalizeCoeffs();
resetFilter();
m_isInitialized = true;
}
template <typename T>
void GenericFilter<T>::normalizeCoeffs()
{
T a0 = m_aCoeff(0);
if (std::abs(a0 - T(1)) < std::numeric_limits<T>::epsilon())
return;
m_aCoeff /= a0;
m_bCoeff /= a0;
}
template <typename T>
bool GenericFilter<T>::checkCoeffs(const vectX_t<T>& aCoeff, const vectX_t<T>& bCoeff, Type type)
{
bool centering = (type == Type::Centered ? (bCoeff.size() % 2 == 1) : true);
return aCoeff.size() > 0 && std::abs(aCoeff[0]) > std::numeric_limits<T>::epsilon() && bCoeff.size() > 0 && centering;
} }
} // namespace difi } // namespace difi

Wyświetl plik

@ -45,47 +45,21 @@ public:
MovingAverage() = default; MovingAverage() = default;
/*! \brief Constructor. /*! \brief Constructor.
* \param windowSize Size of the moving average window. * \param windowSize Size of the moving average window.
* \param type Type of the filter.
*/ */
MovingAverage(int windowSize) MovingAverage(int windowSize, FilterType type = FilterType::Backward)
{ {
setWindowSize(windowSize); setWindowSize(windowSize);
this->setType(type);
} }
/*! \brief Set the size of the moving average window. */ /*! \brief Set the size of the moving average window. */
void setWindowSize(int windowSize) void setWindowSize(int windowSize)
{ {
Expects(windowSize > 0); Expects(windowSize > 0);
setCoeffs(vectX_t<T>::Constant(1, T(1)), vectX_t<T>::Constant(windowSize, T(1) / windowSize)); this->setCoeffs(vectX_t<T>::Constant(1, T(1)), vectX_t<T>::Constant(windowSize, T(1) / static_cast<T>(windowSize)));
} }
/*! \brief Get the size of the moving average window. */ /*! \brief Get the size of the moving average window. */
int windowSize() const noexcept { return bOrder(); } int windowSize() const noexcept { return this->bOrder(); }
};
/*! \brief Centered moving average digital filter.
*
* This is a specialization of a digital filter in order to use a centered moving average.
* \tparam T Floating type.
*/
template <typename T>
class CenteredMovingAverage : public DigitalFilter<T> {
public:
/*! \brief Default uninitialized constructor. */
CenteredMovingAverage() = default;
/*! \brief Constructor.
* \param windowSize Size of the moving average window.
*/
CenteredMovingAverage(int windowSize)
{
setWindowSize(windowSize);
}
/*! \brief Set the size of the moving average window. */
void setWindowSize(int windowSize)
{
Expects(windowSize > 2 && windowSize % 2 == 1);
setCoeffs(vectX_t<T>::Constant(1, T(1)), vectX_t<T>::Constant(windowSize, T(1) / windowSize));
setType(Type::Centered);
}
/*! \brief Get the size of the moving average window. */
int windowSize() const noexcept { return bOrder(); }
}; };
} // namespace difi } // namespace difi

Wyświetl plik

@ -0,0 +1,368 @@
// Copyright (c) 2019, Vincent SAMY
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
#pragma once
#include "DigitalFilter.h"
#include "math_utils.h"
// Read this if you are an adulator of the math god: https://arxiv.org/pdf/1709.08321.pdf
namespace difi {
namespace details {
// T: type
// N: Number of points
// Centered differentiators: http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/central-differences/
template <typename T, int N> struct GetCDCoeffs {
vectN_t<T, N> operator()() const;
};
template <typename T> struct GetCDCoeffs<T, 3> {
vectN_t<T, 3> operator()() const { return (vectN_t<T, 3>() << T(1), T(0), T(-1)).finished() / T(2); }
};
template <typename T> struct GetCDCoeffs<T, 5> {
vectN_t<T, 5> operator()() const { return (vectN_t<T, 5>() << T(-1), T(8), T(0), T(8), T(1)).finished() / T(12); }
};
template <typename T> struct GetCDCoeffs<T, 7> {
vectN_t<T, 7> operator()() const { return (vectN_t<T, 7>() << T(1), T(-9), T(45), T(0), T(-45), T(9), T(-1)).finished() / T(60); }
};
template <typename T> struct GetCDCoeffs<T, 9> {
vectN_t<T, 9> operator()() const { return (vectN_t<T, 9>() << T(-3), T(32), T(-168), T(672), T(0), T(-672), T(168), T(-32), T(3)).finished() / T(840); }
};
// Low-noise Lanczos differentiators: http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/lanczos-low-noise-differentiators/
template <typename T, int N>
struct GetLNLCoeffs {
vectN_t<T, N> operator()() const
{
static_assert(N > 2 && N % 2 == 1, "'N' must be odd.");
constexpr const int M = (N - 1) / 2;
constexpr const int Den = M * (M + 1) * (2 * M + 1);
vectN_t<T, N> v{};
v(M) = T(0);
for (int k = 0; k < M; ++k) {
v(k) = T(3) * static_cast<T>(M - k) / static_cast<T>(Den);
v(N - k - 1) = -v(k);
}
return v;
}
};
// Super Low-noise Lanczos differentiators: http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/lanczos-low-noise-differentiators/
template <typename T, int N> struct GetSLNLCoeffs {
vectN_t<T, N> operator()() const;
};
template <typename T> struct GetSLNLCoeffs<T, 7> {
vectN_t<T, 7> operator()() const { return (vectN_t<T, 7>() << T(-22), T(67), T(58), T(0), T(-58), T(-67), T(22)).finished() / T(252); }
};
template <typename T> struct GetSLNLCoeffs<T, 9> {
vectN_t<T, 9> operator()() const { return (vectN_t<T, 9>() << T(-86), T(142), T(193), T(126), T(0), T(-126), T(-193), T(-142), T(86)).finished() / T(1188); }
};
template <typename T> struct GetSLNLCoeffs<T, 11> {
vectN_t<T, 11> operator()() const { return (vectN_t<T, 11>() << T(-300), T(294), T(532), T(503), T(296), T(0), T(-296), T(-503), T(-532), T(-294), T(300)).finished() / T(5148); }
};
// Backward Noise-Robust differentiators; http://www.holoborodko.com/pavel/wp-content/uploads/OneSidedNoiseRobustDifferentiators.pdf
template <typename T, int N>
struct GetFNRCoeffs {
vectN_t<T, N> operator()() const
{
static_assert(N >= 2, "N should be greater than 2");
constexpr const int BinCoeff = N - 2;
constexpr const int M = N / 2;
vectN_t<T, N> v{};
v(0) = T(1);
v(M) = T(0);
v(N - 1) = T(-1);
for (int i = 1; i < M; ++i) {
v(i) = Binomial<T>(BinCoeff, i) - Binomial<T>(BinCoeff, i - 1);
v(N - i - 1) = -v(i);
}
v /= std::pow(T(2), T(BinCoeff));
return v;
}
};
// Backward Hybrid Noise-Robust differentiators; http://www.holoborodko.com/pavel/wp-content/uploads/OneSidedNoiseRobustDifferentiators.pdf
template <typename T, int N> struct GetFHNRCoeffs {
vectN_t<T, N> operator()() const;
};
template <typename T> struct GetFHNRCoeffs<T, 4> {
vectN_t<T, 4> operator()() const { return (vectN_t<T, 4>() << T(2), T(-1), T(-2), T(1)).finished() / T(2); }
};
template <typename T> struct GetFHNRCoeffs<T, 5> {
vectN_t<T, 5> operator()() const { return (vectN_t<T, 5>() << T(7), T(1), T(-10), T(-1), T(3)).finished() / T(10); }
};
template <typename T> struct GetFHNRCoeffs<T, 6> {
vectN_t<T, 6> operator()() const { return (vectN_t<T, 6>() << T(16), T(1), T(-10), T(-10), T(-6), T(9)).finished() / T(28); }
};
template <typename T> struct GetFHNRCoeffs<T, 7> {
vectN_t<T, 7> operator()() const { return (vectN_t<T, 7>() << T(12), T(5), T(-8), T(-6), T(-10), T(1), T(6)).finished() / T(28); }
};
template <typename T> struct GetFHNRCoeffs<T, 8> {
vectN_t<T, 8> operator()() const { return (vectN_t<T, 8>() << T(22), T(7), T(-6), T(-11), T(-14), T(-9), T(-2), T(13)).finished() / T(60); }
};
template <typename T> struct GetFHNRCoeffs<T, 9> {
vectN_t<T, 9> operator()() const { return (vectN_t<T, 9>() << T(52), T(29), T(-14), T(-17), T(-40), T(-23), T(-26), T(11), T(28)).finished() / T(180); }
};
template <typename T> struct GetFHNRCoeffs<T, 10> {
vectN_t<T, 10> operator()() const { return (vectN_t<T, 10>() << T(56), T(26), T(-2), T(-17), T(-30), T(-30), T(-28), T(-13), T(4), T(34)).finished() / T(220); }
};
template <typename T> struct GetFHNRCoeffs<T, 11> {
vectN_t<T, 11> operator()() const { return (vectN_t<T, 11>() << T(320), T(206), T(-8), T(-47), T(-186), T(-150), T(-214), T(-103), T(-92), T(94), T(180)).finished() / T(1540); }
};
template <typename T> struct GetFHNRCoeffs<T, 16> {
vectN_t<T, 16> operator()() const { return (vectN_t<T, 16>() << T(322), T(217), T(110), T(35), T(-42), T(-87), T(-134), T(-149), T(-166), T(-151), T(-138), T(-93), T(-50), T(28), T(98), T(203)).finished() / T(2856); }
};
template <typename T, int N, typename BackwardCoeffs> vectN_t<T, N> GetBackwardISDCoeffs()
{
vectN_t<T, N> v{};
const vectN_t<T, N> v0 = BackwardCoeffs{}();
for (Eigen::Index k = 0; k < N; ++k)
v(k) = k * v0(k);
return v;
}
// Backward Noise-Robust differentiators for irregular space data
template <typename T, int N> struct GetFNRISDCoeffs {
vectN_t<T, N> operator()() const { return GetBackwardISDCoeffs<T, N, GetFNRCoeffs<T, N>>(); }
};
// Backward Hybrid Noise-Robust differentiators for irregular space data
template <typename T, int N> struct GetFHNRISDCoeffs {
vectN_t<T, N> operator()() const { return GetBackwardISDCoeffs<T, N, GetFHNRCoeffs<T, N>>(); }
};
// Centered Noise-Robust differentiators (tangency at 2nd order): http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/smooth-low-noise-differentiators/
template <typename T, int N>
struct GetCNR2Coeffs {
vectN_t<T, N> operator()() const
{
static_assert(N % 2 == 1., "'N' must be odd.");
return GetFNRCoeffs<T, N>{}(); // Same coefficients
}
};
// Centered Noise-Robust differentiators (tangency at 4th order): http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/smooth-low-noise-differentiators/
template <typename T, int N> struct GetCNR4Coeffs {
vectN_t<T, N> operator()() const;
};
template <typename T> struct GetCNR4Coeffs<T, 7> {
vectN_t<T, 7> operator()() const { return (vectN_t<T, 7>() << T(-5), T(12), T(39), T(0), T(-39), T(-12), T(5)).finished() / T(96); }
};
template <typename T> struct GetCNR4Coeffs<T, 9> {
vectN_t<T, 9> operator()() const { return (vectN_t<T, 9>() << T(-2), T(-1), T(16), T(27), T(0), T(-27), T(-16), T(1), T(2)).finished() / T(96); }
};
template <typename T> struct GetCNR4Coeffs<T, 11> {
vectN_t<T, 11> operator()() const { return (vectN_t<T, 11>() << T(-11), T(-32), T(39), T(256), T(322), T(0), T(-322), T(-256), T(-39), T(32), T(11)).finished() / T(1536); }
};
// Centered Noise-Robust differentiators for irregular space data: http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/smooth-low-noise-differentiators/
template <typename T, int N, typename CNRCoeffs> vectN_t<T, N> GetCNRISDCoeffs()
{
constexpr const int M = (N - 1) / 2;
vectN_t<T, N> v{};
const vectN_t<T, N> v0 = CNRCoeffs{}();
v(M) = 0;
for (int k = 1; k < M + 1; ++k) {
v(M - k) = T(2) * k * v0(M - k);
v(M + k) = T(2) * k * v0(M + k);
}
return v;
}
template <typename T, int N> struct GetCNR2ISDCoeffs {
vectN_t<T, N> operator()() const { return GetCNRISDCoeffs<T, N, GetCNR2Coeffs<T, N>>(); }
};
template <typename T, int N> struct GetCNR4ISDCoeffs {
vectN_t<T, N> operator()() const { return GetCNRISDCoeffs<T, N, GetCNR4Coeffs<T, N>>(); }
};
/*
* Second order differentiators
*/
template <typename T>
constexpr T GetSONRBaseCoeff(int N, int M, int k)
{
if (k > M)
return T(0);
else if (k == M)
return T(1);
else
return ((T(2) * N - T(10)) * GetSONRBaseCoeff<T>(N, M, k + 1) - (N + T(2) * k + T(3)) * GetSONRBaseCoeff<T>(N, M, k + 2)) / (N - T(2) * k - T(1));
}
template <typename T, int N> vectN_t<T, (N - 1) / 2 + 1> GetSONRBaseCoeffs()
{
static_assert(N >= 5 && N % 2 == 1, "N must be a odd number >= 5");
constexpr const int M = (N - 1) / 2;
vectN_t<T, M + 1> s{};
for (int k = 0; k < M + 1; ++k)
s(k) = GetSONRBaseCoeff<T>(N, M, k);
return s;
}
// Second-Order Centered Noise-Robust differentiator: http://www.holoborodko.com/pavel/downloads/NoiseRobustSecondDerivative.pdf
template <typename T, int N>
struct GetSOCNRCoeffs {
vectN_t<T, N> operator()() const
{
constexpr const int M = (N - 1) / 2;
constexpr const T Den = pow(2, N - 3);
vectN_t<T, N> v{};
vectN_t<T, M + 1> s = GetSONRBaseCoeffs<T, N>();
v(M) = s(0);
for (int k = 1; k < M + 1; ++k) {
v(M + k) = s(k);
v(M - k) = s(k);
}
v /= Den;
return v;
}
};
// Second-Order Backward Noise-Robust differentiator: http://www.holoborodko.com/pavel/downloads/NoiseRobustSecondDerivative.pdf
template <typename T, int N> struct GetSOFNRCoeffs {
vectN_t<T, N> operator()() const { return GetSOCNRCoeffs<T, N>(); }
}; // Coefficients are the same.
// Second-Order Centered Noise-Robust Irregular Space Data differentiator: http://www.holoborodko.com/pavel/downloads/NoiseRobustSecondDerivative.pdf
template <typename T, int N>
struct GetSOCNRISDCoeffs {
vectN_t<T, N> operator()() const
{
constexpr const int M = (N - 1) / 2;
constexpr const T Den = pow(2, N - 3);
vectN_t<T, N> v{};
const vectN_t<T, M + 1> s = GetSONRBaseCoeffs<T, N>();
const auto alpha = [&s](int k) -> T { return T(4) * k * k * s(k); };
v(M) = T(0);
for (int k = 1; k < M + 1; ++k) {
auto alph = alpha(k);
v(M) -= T(2) * alph;
v(M + k) = alph;
v(M - k) = alph;
}
v /= Den;
return v;
}
};
// Second-Order Backward Noise-Robust Irregular Space Data differentiator: http://www.holoborodko.com/pavel/downloads/NoiseRobustSecondDerivative.pdf
template <typename T, int N> struct GetSOFNRISDCoeffs {
vectN_t<T, N> operator()() const { return GetSOCNRISDCoeffs<T, N>(); }
}; // Same coefficients
/*
* Differentiator Generator
*/
template <typename T, int N, int Order, typename CoeffGetter>
class BackwardDifferentiator : public GenericFilter<T> {
public:
BackwardDifferentiator()
: GenericFilter<T>(vectX_t<T>::Constant(1, T(1)), CoeffGetter{}())
{}
BackwardDifferentiator(T timestep)
: GenericFilter<T>(vectX_t<T>::Constant(1, T(1)), CoeffGetter{}() / std::pow(timestep, Order))
{}
void setTimestep(T timestep) { this->setCoeffs(vectX_t<T>::Constant(1, T(1)), CoeffGetter{}() / std::pow(timestep, Order)); }
T timestep() const noexcept { return std::pow(this->bCoeff()(0) / CoeffGetter{}()(0), T(1) / Order); }
};
template <typename T, int N, int Order, typename CoeffGetter>
class CenteredDifferentiator : public GenericFilter<T> {
public:
CenteredDifferentiator()
: GenericFilter<T>(vectX_t<T>::Constant(1, T(1)), CoeffGetter{}(), FilterType::Centered)
{}
CenteredDifferentiator(T timestep)
: GenericFilter<T>(vectX_t<T>::Constant(1, T(1)), CoeffGetter{}() / std::pow(timestep, Order))
{}
void setTimestep(T timestep) { this->setCoeffs(vectX_t<T>::Constant(1, T(1)), CoeffGetter{}() / std::pow(timestep, Order)); }
T timestep() const noexcept { return std::pow(this->bCoeff()(0) / CoeffGetter{}()(0), T(1) / Order); }
};
template <typename T, int N, int Order, typename CoeffGetter>
class TVBackwardDifferentiator : public TVGenericFilter<T> {
static_assert(Order >= 1, "Order must be greater or equal to 1");
public:
TVBackwardDifferentiator()
: TVGenericFilter<T>(Order, vectX_t<T>::Constant(1, T(1)), CoeffGetter{}())
{}
};
template <typename T, int N, int Order, typename CoeffGetter>
class TVCenteredDifferentiator : public TVGenericFilter<T> {
static_assert(Order >= 1, "Order must be greater or equal to 1");
public:
TVCenteredDifferentiator()
: TVGenericFilter<T>(Order, vectX_t<T>::Constant(1, T(1)), CoeffGetter{}(), FilterType::Centered)
{}
};
} // namespace details
// Backward differentiators
template <typename T, int N> using BackwardDiffNoiseRobust = details::BackwardDifferentiator<T, N, 1, details::GetFNRCoeffs<T, N>>;
template <typename T, int N> using BackwardDiffHybridNoiseRobust = details::BackwardDifferentiator<T, N, 1, details::GetFHNRCoeffs<T, N>>;
// Time-Varying backward differentiators
template <typename T, int N> using TVBackwardDiffNoiseRobust = details::TVBackwardDifferentiator<T, N, 1, details::GetFNRISDCoeffs<T, N>>;
template <typename T, int N> using TVBackwardDiffHybridNoiseRobust = details::TVBackwardDifferentiator<T, N, 1, details::GetFHNRISDCoeffs<T, N>>;
// Centered differentiators
template <typename T, int N> using CenteredDiffBasic = details::CenteredDifferentiator<T, N, 1, details::GetCDCoeffs<T, N>>;
template <typename T, int N> using CenteredDiffLowNoiseLanczos = details::CenteredDifferentiator<T, N, 1, details::GetLNLCoeffs<T, N>>;
template <typename T, int N> using CenteredDiffSuperLowNoiseLanczos = details::CenteredDifferentiator<T, N, 1, details::GetSLNLCoeffs<T, N>>;
template <typename T, int N> using CenteredDiffNoiseRobust2 = details::CenteredDifferentiator<T, N, 1, details::GetCNR2Coeffs<T, N>>;
template <typename T, int N> using CenteredDiffNoiseRobust4 = details::CenteredDifferentiator<T, N, 1, details::GetCNR4Coeffs<T, N>>;
// Time-Varying centered differentiators
template <typename T, int N> using TVCenteredDiffNoiseRobust2 = details::TVCenteredDifferentiator<T, N, 1, details::GetCNR2ISDCoeffs<T, N>>;
template <typename T, int N> using TVCenteredDiffNoiseRobust4 = details::TVCenteredDifferentiator<T, N, 1, details::GetCNR4ISDCoeffs<T, N>>;
// Second-order backward differentiators
template <typename T, int N> using BackwardDiffSecondOrder = details::BackwardDifferentiator<T, N, 2, details::GetSOFNRCoeffs<T, N>>;
// Second-order Time-Varying backward differentiators
template <typename T, int N> using TVBackwardDiffSecondOrder = details::TVBackwardDifferentiator<T, N, 2, details::GetSOFNRISDCoeffs<T, N>>;
// Second-order centered differentiators
template <typename T, int N> using CenteredDiffSecondOrder = details::CenteredDifferentiator<T, N, 2, details::GetSOCNRCoeffs<T, N>>;
// Second-order Time-Varying centered differentiators
template <typename T, int N> using TVCenteredDiffSecondOrder = details::TVCenteredDifferentiator<T, N, 2, details::GetSOCNRISDCoeffs<T, N>>;
} // namespace difi

Wyświetl plik

@ -32,6 +32,7 @@
#include "DigitalFilter.h" #include "DigitalFilter.h"
#include "GenericFilter.h" #include "GenericFilter.h"
#include "MovingAverage.h" #include "MovingAverage.h"
#include "differentiators.h"
#include "polynome_functions.h" #include "polynome_functions.h"
#include "typedefs.h" #include "typedefs.h"
@ -59,4 +60,42 @@ using BilinearTransformd = BilinearTransform<double>;
using BilinearTransformcf = BilinearTransform<std::complex<float>>; using BilinearTransformcf = BilinearTransform<std::complex<float>>;
using BilinearTransformcd = BilinearTransform<std::complex<double>>; using BilinearTransformcd = BilinearTransform<std::complex<double>>;
// 1st order centered differentiators
template <int N> using CenteredDiffBasicf = CenteredDiffBasic<float, N>;
template <int N> using CenteredDiffBasicd = CenteredDiffBasic<double, N>;
template <int N> using CenteredDiffLowNoiseLanczosf = CenteredDiffLowNoiseLanczos<float, N>;
template <int N> using CenteredDiffLowNoiseLanczosd = CenteredDiffLowNoiseLanczos<double, N>;
template <int N> using CenteredDiffSuperLowNoiseLanczosf = CenteredDiffSuperLowNoiseLanczos<float, N>;
template <int N> using CenteredDiffSuperLowNoiseLanczosd = CenteredDiffSuperLowNoiseLanczos<double, N>;
template <int N> using CenteredDiffNoiseRobust2f = CenteredDiffNoiseRobust2<float, N>;
template <int N> using CenteredDiffNoiseRobust2d = CenteredDiffNoiseRobust2<double, N>;
template <int N> using CenteredDiffNoiseRobust4f = CenteredDiffNoiseRobust4<float, N>;
template <int N> using CenteredDiffNoiseRobust4d = CenteredDiffNoiseRobust4<double, N>;
template <int N> using TVCenteredDiffNoiseRobust2f = TVCenteredDiffNoiseRobust2<float, N>;
template <int N> using TVCenteredDiffNoiseRobust2d = TVCenteredDiffNoiseRobust2<double, N>;
template <int N> using TVCenteredDiffNoiseRobust4f = TVCenteredDiffNoiseRobust4<float, N>;
template <int N> using TVCenteredDiffNoiseRobust4d = TVCenteredDiffNoiseRobust4<double, N>;
// 2nd order centered differentiators
template <int N> using CenteredDiffSecondOrderf = CenteredDiffSecondOrder<float, N>;
template <int N> using CenteredDiffSecondOrderd = CenteredDiffSecondOrder<double, N>;
template <int N> using TVCenteredDiffSecondOrderf = TVCenteredDiffSecondOrder<float, N>;
template <int N> using TVCenteredDiffSecondOrderd = TVCenteredDiffSecondOrder<double, N>;
// 1st order backward differentiators
template <int N> using BackwardDiffNoiseRobustf = BackwardDiffNoiseRobust<float, N>;
template <int N> using BackwardDiffNoiseRobustd = BackwardDiffNoiseRobust<double, N>;
template <int N> using BackwardDiffHybridNoiseRobustf = BackwardDiffHybridNoiseRobust<float, N>;
template <int N> using BackwardDiffHybridNoiseRobustd = BackwardDiffHybridNoiseRobust<double, N>;
template <int N> using TVBackwardDiffNoiseRobustf = TVBackwardDiffNoiseRobust<float, N>;
template <int N> using TVBackwardDiffNoiseRobustd = TVBackwardDiffNoiseRobust<double, N>;
template <int N> using TVBackwardDiffHybridNoiseRobustf = TVBackwardDiffHybridNoiseRobust<float, N>;
template <int N> using TVBackwardDiffHybridNoiseRobustd = TVBackwardDiffHybridNoiseRobust<double, N>;
// 2nd order backward differentiators
template <int N> using BackwardDiffSecondOrderf = BackwardDiffSecondOrder<float, N>;
template <int N> using BackwardDiffSecondOrderd = BackwardDiffSecondOrder<double, N>;
template <int N> using TVBackwardDiffSecondOrderf = TVBackwardDiffSecondOrder<float, N>;
template <int N> using TVBackwardDiffSecondOrderd = TVBackwardDiffSecondOrder<double, N>;
} // namespace difi } // namespace difi

Wyświetl plik

@ -0,0 +1,61 @@
// Copyright (c) 2019, Vincent SAMY
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
#pragma once
#include <type_traits>
namespace difi {
// https://stackoverflow.com/questions/44718971/calculate-binomial-coffeficient-very-reliably
template <typename T>
constexpr T Binomial(int n, int k)
{
if (k > n)
return T(0);
else if (k == 0 || k == n)
return T(1);
else if (k == 1 || k == n - 1)
return T(n);
else if (2 * k < n)
return Binomial<T>(n - 1, k - 1) * n / k;
else
return Binomial<T>(n - 1, k) * n / (n - k);
}
template <typename T>
constexpr T pow(T n, T k)
{
static_assert(std::is_integral<T>::value, "For integral values only, eitherwise, use std::pow");
if (n == 0)
return (k == 0) ? 1 : 0;
else if (k == 0)
return T(1);
else
return n * pow(n, k - 1);
}
} // namespace difi

Wyświetl plik

@ -37,7 +37,15 @@ constexpr T pi = T(3.14159265358979323846264338327950288419716939937510582097494
template <typename T> template <typename T>
using vectX_t = Eigen::Matrix<T, Eigen::Dynamic, 1>; /*!< Eigen column-vector */ using vectX_t = Eigen::Matrix<T, Eigen::Dynamic, 1>; /*!< Eigen column-vector */
template <typename T, int N>
using vectN_t = Eigen::Matrix<T, N, 1>; /*!< Fixed Eigen column-vector */
template <typename T> template <typename T>
using vectXc_t = vectX_t<std::complex<T>>; /*!< Eigen complex column-vector */ using vectXc_t = vectX_t<std::complex<T>>; /*!< Eigen complex column-vector */
enum class FilterType {
Backward,
Centered
};
} // namespace difi } // namespace difi

Wyświetl plik

@ -25,14 +25,13 @@
// of the authors and should not be interpreted as representing official policies, // of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project. // either expressed or implied, of the FreeBSD Project.
#define BOOST_TEST_MODULE ButterworthFilterTests
// Note: In term of precision, LP > HP > BP ~= BR // Note: In term of precision, LP > HP > BP ~= BR
#include "difi" #include "difi"
#include "doctest/doctest.h"
#include "doctest_helper.h"
#include "test_functions.h" #include "test_functions.h"
#include "warning_macro.h" #include "warning_macro.h"
#include <boost/test/unit_test.hpp>
DISABLE_CONVERSION_WARNING_BEGIN DISABLE_CONVERSION_WARNING_BEGIN
@ -64,92 +63,51 @@ struct System {
DISABLE_CONVERSION_WARNING_END DISABLE_CONVERSION_WARNING_END
BOOST_AUTO_TEST_CASE(FIND_BUTTERWORTH_LP_HP_FLOAT) TEST_CASE_TEMPLATE("Find butterworth Low pass and High pass", T, float, double)
{ {
// LP // LP
auto butterRequirement = difi::Butterworthf::findMinimumButter(40.f / 500.f, 150.f / 500.f, 3.f, 60.f); auto butterRequirement = difi::Butterworth<T>::findMinimumButter(T(40. / 500.), T(150. / 500.), T(3.), T(60.));
BOOST_REQUIRE_EQUAL(5, butterRequirement.first); REQUIRE_EQUAL(5, butterRequirement.first);
BOOST_REQUIRE_SMALL(std::abs(static_cast<float>(0.081038494957764) - butterRequirement.second), std::numeric_limits<float>::epsilon() * 10); REQUIRE_SMALL(std::abs(T(0.081038494957764) - butterRequirement.second), std::numeric_limits<T>::epsilon() * 10);
// HP // HP
butterRequirement = difi::Butterworthf::findMinimumButter(150.f / 500.f, 40.f / 500.f, 3.f, 60.f); butterRequirement = difi::Butterworth<T>::findMinimumButter(T(150. / 500.), T(40. / 500.), T(3.), T(60.));
BOOST_REQUIRE_EQUAL(5, butterRequirement.first); REQUIRE_EQUAL(5, butterRequirement.first);
BOOST_REQUIRE_SMALL(std::abs(static_cast<float>(0.296655824107340) - butterRequirement.second), std::numeric_limits<float>::epsilon() * 10); REQUIRE_SMALL(std::abs(T(0.296655824107340) - butterRequirement.second), std::numeric_limits<T>::epsilon() * 10);
} }
BOOST_AUTO_TEST_CASE(FIND_BUTTERWORTH_LP_HP_DOUBLE) TEST_CASE_TEMPLATE("Butterworth low pass filter", T, float, double)
{ {
// LP System<T> s;
auto butterRequirement = difi::Butterworthd::findMinimumButter(40. / 500., 150. / 500., 3., 60.); auto bf = difi::Butterworth<T>(s.order, s.fc, s.fs);
BOOST_REQUIRE_EQUAL(5, butterRequirement.first); REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
BOOST_REQUIRE_SMALL(std::abs(0.081038494957764 - butterRequirement.second), std::numeric_limits<double>::epsilon() * 10); test_coeffs(s.lpACoeffRes, s.lpBCoeffRes, bf, std::numeric_limits<T>::epsilon() * 10);
test_results(s.lpResults, s.data, bf, std::numeric_limits<T>::epsilon() * 100);
// HP
butterRequirement = difi::Butterworthd::findMinimumButter(150. / 500., 40. / 500., 3., 60.);
BOOST_REQUIRE_EQUAL(5, butterRequirement.first);
BOOST_REQUIRE_SMALL(std::abs(0.296655824107340 - butterRequirement.second), std::numeric_limits<double>::epsilon() * 10);
} }
BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_LP_FILTER_FLOAT, System<float>) TEST_CASE_TEMPLATE("Butterworth high pass filter", T, float, double)
{ {
auto bf = difi::Butterworthf(order, fc, fs); System<T> s;
BOOST_REQUIRE_EQUAL(bf.aOrder(), bf.bOrder()); auto bf = difi::Butterworth<T>(s.order, s.fc, s.fs, difi::Butterworth<T>::Type::HighPass);
test_coeffs(lpACoeffRes, lpBCoeffRes, bf, std::numeric_limits<float>::epsilon() * 10); REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
test_results(lpResults, data, bf, std::numeric_limits<float>::epsilon() * 100); test_coeffs(s.hpACoeffRes, s.hpBCoeffRes, bf, std::numeric_limits<T>::epsilon() * 10);
test_results(s.hpResults, s.data, bf, std::numeric_limits<T>::epsilon() * 1000);
} }
BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_LP_FILTER_DOUBLE, System<double>) TEST_CASE_TEMPLATE("Butterworth band pass filter", T, float, double)
{ {
auto bf = difi::Butterworthd(order, fc, fs); System<T> s;
BOOST_REQUIRE_EQUAL(bf.aOrder(), bf.bOrder()); auto bf = difi::Butterworth<T>(s.order, s.fLower, s.fUpper, s.fs);
test_coeffs(lpACoeffRes, lpBCoeffRes, bf, std::numeric_limits<double>::epsilon() * 10); REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
test_results(lpResults, data, bf, std::numeric_limits<double>::epsilon() * 100); test_coeffs(s.bpACoeffRes, s.bpBCoeffRes, bf, std::numeric_limits<T>::epsilon() * 1000);
test_results(s.bpResults, s.data, bf, std::numeric_limits<T>::epsilon() * 10000);
} }
BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_HP_FILTER_FLOAT, System<float>) TEST_CASE_TEMPLATE("Butterworth band-reject filter", T, float, double)
{ {
auto bf = difi::Butterworthf(order, fc, fs, difi::Butterworthf::Type::HighPass); System<T> s;
BOOST_REQUIRE_EQUAL(bf.aOrder(), bf.bOrder()); auto bf = difi::Butterworth<T>(s.order, s.fLower, s.fUpper, s.fs, difi::Butterworth<T>::Type::BandReject);
test_coeffs(hpACoeffRes, hpBCoeffRes, bf, std::numeric_limits<float>::epsilon() * 10); REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
test_results(hpResults, data, bf, std::numeric_limits<float>::epsilon() * 1000); test_coeffs(s.brACoeffRes, s.brBCoeffRes, bf, std::numeric_limits<T>::epsilon() * T(1e8));
} test_results(s.brResults, s.data, bf, std::numeric_limits<T>::epsilon() * T(1e8));
BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_HP_FILTER_DOUBLE, System<double>)
{
auto bf = difi::Butterworthd(order, fc, fs, difi::Butterworthd::Type::HighPass);
BOOST_REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
test_coeffs(hpACoeffRes, hpBCoeffRes, bf, std::numeric_limits<double>::epsilon() * 10);
test_results(hpResults, data, bf, std::numeric_limits<double>::epsilon() * 100);
}
BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_BP_FILTER_FLOAT, System<float>)
{
auto bf = difi::Butterworthf(order, fLower, fUpper, fs);
BOOST_REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
test_coeffs(bpACoeffRes, bpBCoeffRes, bf, std::numeric_limits<float>::epsilon() * 1000);
test_results(bpResults, data, bf, std::numeric_limits<float>::epsilon() * 10000);
}
BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_BP_FILTER_DOUBLE, System<double>)
{
auto bf = difi::Butterworthd(order, fLower, fUpper, fs);
BOOST_REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
test_coeffs(bpACoeffRes, bpBCoeffRes, bf, std::numeric_limits<double>::epsilon() * 1000);
test_results(bpResults, data, bf, std::numeric_limits<double>::epsilon() * 10000);
}
BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_BR_FILTER_FLOAT, System<float>)
{
auto bf = difi::Butterworthf(order, fLower, fUpper, fs, difi::Butterworthf::Type::BandReject);
BOOST_REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
test_coeffs(brACoeffRes, brBCoeffRes, bf, 1.f);
test_results(brResults, data, bf, 1.f);
}
BOOST_FIXTURE_TEST_CASE(BUTTERWORTH_BR_FILTER_DOUBLE, System<double>)
{
auto bf = difi::Butterworthd(order, fLower, fUpper, fs, difi::Butterworthd::Type::BandReject);
BOOST_REQUIRE_EQUAL(bf.aOrder(), bf.bOrder());
test_coeffs(brACoeffRes, brBCoeffRes, bf, 1e-8);
test_results(brResults, data, bf, 1e-8);
} }

Wyświetl plik

@ -27,20 +27,13 @@
enable_testing() enable_testing()
if(${BUILD_TEST_STATIC_BOOST})
set(Boost_USE_STATIC_LIBS ON)
set(BUILD_SHARED_LIBS OFF)
set(BOOST_DEFS "")
else()
set(Boost_USE_STATIC_LIBS OFF)
set(BUILD_SHARED_LIBS ON)
set(BOOST_DEFS Boost::dynamic_linking)
endif()
find_package(Boost REQUIRED COMPONENTS unit_test_framework)
macro(addTest testName) macro(addTest testName)
add_executable(${testName} ${testName}.cpp) add_executable(${testName} ${testName}.cpp)
target_link_libraries(${testName} PRIVATE Boost::unit_test_framework Boost::disable_autolinking ${BOOST_DEFS} ${PROJECT_NAME}) if (MSVC)
target_compile_definitions(${testName} PUBLIC _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS)
endif()
target_compile_definitions(${testName} PUBLIC DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN)
target_link_libraries(${testName} PUBLIC Eigen3::Eigen)
# Adding a project configuration file (for MSVC only) # Adding a project configuration file (for MSVC only)
generate_msvc_dot_user_file(${testName}) generate_msvc_dot_user_file(${testName})
@ -51,4 +44,7 @@ addTest(GenericFilterTests)
addTest(polynome_functions_tests) addTest(polynome_functions_tests)
addTest(DigitalFilterTests) addTest(DigitalFilterTests)
addTest(MovingAverageFilterTests) addTest(MovingAverageFilterTests)
addTest(ButterWorthFilterTests) addTest(ButterworthFilterTests)
# Differentiators
addTest(differentiator_tests)

Wyświetl plik

@ -25,12 +25,10 @@
// of the authors and should not be interpreted as representing official policies, // of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project. // either expressed or implied, of the FreeBSD Project.
#define BOOST_TEST_MODULE DigitalFilterTests
#include "difi" #include "difi"
#include "doctest/doctest.h"
#include "test_functions.h" #include "test_functions.h"
#include "warning_macro.h" #include "warning_macro.h"
#include <boost/test/unit_test.hpp>
DISABLE_CONVERSION_WARNING_BEGIN DISABLE_CONVERSION_WARNING_BEGIN
@ -44,16 +42,10 @@ struct System {
DISABLE_CONVERSION_WARNING_END DISABLE_CONVERSION_WARNING_END
BOOST_FIXTURE_TEST_CASE(DIGITAL_FILTER_FLOAT, System<float>) TEST_CASE_TEMPLATE("Digital filter", T, float, double)
{ {
auto df = difi::DigitalFilterf(aCoeff, bCoeff); System<T> s;
test_coeffs(aCoeff, bCoeff, df, std::numeric_limits<float>::epsilon() * 10); auto df = difi::DigitalFilter<T>(s.aCoeff, s.bCoeff);
test_results(results, data, df, std::numeric_limits<float>::epsilon() * 10); test_coeffs(s.aCoeff, s.bCoeff, df, std::numeric_limits<T>::epsilon() * 10);
} test_results(s.results, s.data, df, std::numeric_limits<T>::epsilon() * 10);
BOOST_FIXTURE_TEST_CASE(DIGITAL_FILTER_DOUBLE, System<double>)
{
auto df = difi::DigitalFilterd(aCoeff, bCoeff);
test_coeffs(aCoeff, bCoeff, df, std::numeric_limits<double>::epsilon() * 10);
test_results(results, data, df, std::numeric_limits<double>::epsilon() * 10);
} }

Wyświetl plik

@ -25,35 +25,52 @@
// of the authors and should not be interpreted as representing official policies, // of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project. // either expressed or implied, of the FreeBSD Project.
#define BOOST_TEST_MODULE GenericFilterTests
#include "difi" #include "difi"
#include <boost/test/unit_test.hpp> #include "doctest/doctest.h"
#include <exception> #include <exception>
#include <vector> #include <vector>
BOOST_AUTO_TEST_CASE(FILTER_FAILURES) TEST_CASE("Filter failures")
{ {
// A coeff are missing // A coeff are missing
BOOST_REQUIRE_THROW(difi::DigitalFilterd(Eigen::VectorXd(), Eigen::VectorXd::Constant(2, 0)), std::logic_error); REQUIRE_THROWS_AS(difi::DigitalFilterd(Eigen::VectorXd(), Eigen::VectorXd::Constant(2, 0)), std::logic_error);
// B coeff are missing // B coeff are missing
BOOST_REQUIRE_THROW(difi::DigitalFilterd(Eigen::VectorXd::Constant(2, 1), Eigen::VectorXd()), std::logic_error); REQUIRE_THROWS_AS(difi::DigitalFilterd(Eigen::VectorXd::Constant(2, 1), Eigen::VectorXd()), std::logic_error);
// aCoeff(0) = 0 // aCoeff(0) = 0
BOOST_REQUIRE_THROW(difi::DigitalFilterd(Eigen::VectorXd::Constant(2, 0), Eigen::VectorXd::Constant(2, 0)), std::logic_error); REQUIRE_THROWS_AS(difi::DigitalFilterd(Eigen::VectorXd::Constant(2, 0), Eigen::VectorXd::Constant(2, 0)), std::logic_error);
// Filter left uninitialized // Filter left uninitialized
BOOST_REQUIRE_NO_THROW(difi::DigitalFilterd()); REQUIRE_NOTHROW(difi::DigitalFilterd());
auto df = difi::DigitalFilterd(); auto df = difi::DigitalFilterd();
// Filter data with uninitialized filter // Filter data with uninitialized filter
BOOST_REQUIRE_THROW(df.stepFilter(10.), std::logic_error); REQUIRE_THROWS_AS(df.stepFilter(10.), std::logic_error);
// Change type before settings coeffs (The difi::FilterType::Centered needs to have odd number of bCoeffs)
REQUIRE_THROWS_AS(df.setType(difi::FilterType::Centered), std::logic_error);
// Set type to difi::FilterType::Backward is always ok.
REQUIRE_NOTHROW(df.setType(difi::FilterType::Backward));
// Set even number of bCoeffs
REQUIRE_NOTHROW(df.setCoeffs(Eigen::VectorXd::Constant(2, 1), Eigen::VectorXd::Constant(2, 0)));
// Check again
REQUIRE_THROWS_AS(df.setType(difi::FilterType::Centered), std::logic_error);
// Set odd number of bCoeffs
REQUIRE_NOTHROW(df.setCoeffs(Eigen::VectorXd::Constant(2, 1), Eigen::VectorXd::Constant(3, 0)));
// Check again
REQUIRE_NOTHROW(df.setType(difi::FilterType::Centered));
// Put even number of bCoeffs on a Centered filter
REQUIRE_THROWS_AS(df.setCoeffs(Eigen::VectorXd::Constant(2, 1), Eigen::VectorXd::Constant(2, 0)), std::logic_error);
// Put odd number of bCoeffs
REQUIRE_NOTHROW(df.setCoeffs(Eigen::VectorXd::Constant(5, 2), Eigen::VectorXd::Constant(7, 3)));
// window <= 0 // window <= 0
BOOST_REQUIRE_THROW(difi::MovingAveraged(0), std::logic_error); REQUIRE_THROWS_AS(difi::MovingAveraged(0), std::logic_error);
// order <= 0 // order <= 0
BOOST_REQUIRE_THROW(difi::Butterworthd(0, 10, 100), std::logic_error); REQUIRE_THROWS_AS(difi::Butterworthd(0, 10, 100), std::logic_error);
// fc > 2*fs // fc > 2*fs
BOOST_REQUIRE_THROW(difi::Butterworthd(2, 60, 100), std::logic_error); REQUIRE_THROWS_AS(difi::Butterworthd(2, 60, 100), std::logic_error);
// Upper frequency < lower frequency // Upper frequency < lower frequency
BOOST_REQUIRE_THROW(difi::Butterworthd(2, 6, 5, 100), std::logic_error); REQUIRE_THROWS_AS(difi::Butterworthd(2, 6, 5, 100), std::logic_error);
// Ok // Ok
BOOST_REQUIRE_NO_THROW(difi::DigitalFilterd(Eigen::VectorXd::Constant(2, 1), Eigen::VectorXd::Constant(2, 0))); REQUIRE_NOTHROW(difi::DigitalFilterd(Eigen::VectorXd::Constant(2, 1), Eigen::VectorXd::Constant(2, 0)));
// Bad type. Need odd number of bCoeffs
REQUIRE_THROWS_AS(difi::DigitalFilterd(Eigen::VectorXd::Constant(2, 1), Eigen::VectorXd::Constant(2, 0), difi::FilterType::Centered), std::logic_error);
} }

Wyświetl plik

@ -25,11 +25,9 @@
// of the authors and should not be interpreted as representing official policies, // of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project. // either expressed or implied, of the FreeBSD Project.
#define BOOST_TEST_MODULE MovingAverageFilterTests
#include "difi" #include "difi"
#include "doctest/doctest.h"
#include "test_functions.h" #include "test_functions.h"
#include <boost/test/unit_test.hpp>
template <typename T> template <typename T>
struct System { struct System {
@ -38,14 +36,9 @@ struct System {
difi::vectX_t<T> results = (difi::vectX_t<T>(6) << 0.25, 0.75, 1.5, 2.5, 3.5, 4.5).finished(); difi::vectX_t<T> results = (difi::vectX_t<T>(6) << 0.25, 0.75, 1.5, 2.5, 3.5, 4.5).finished();
}; };
BOOST_FIXTURE_TEST_CASE(MOVING_AVERAGE_FLOAT, System<float>) TEST_CASE_TEMPLATE("Moving average filter", T, float, double)
{ {
auto maf = difi::MovingAveragef(windowSize); System<T> s;
test_results(results, data, maf, std::numeric_limits<float>::epsilon() * 10); auto maf = difi::MovingAverage<T>(s.windowSize);
} test_results(s.results, s.data, maf, std::numeric_limits<T>::epsilon() * 10);
}
BOOST_FIXTURE_TEST_CASE(MOVING_AVERAGE_DOUBLE, System<double>)
{
auto maf = difi::MovingAveraged(windowSize);
test_results(results, data, maf, std::numeric_limits<double>::epsilon() * 10);
}

277
tests/diffTesters.h 100644
Wyświetl plik

@ -0,0 +1,277 @@
// Copyright (c) 2019, Vincent SAMY
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
#pragma once
#include "doctest_helper.h"
#include "difi"
#include <array>
#include "doctest/doctest.h"
using namespace difi;
namespace tester {
namespace details {
template <typename Tuple, size_t Index>
struct set_time_step {
static void impl(Tuple& filters, double dt)
{
std::get<Index>(filters).setTimestep(dt);
set_time_step<Tuple, Index - 1>::impl(filters, dt);
}
};
template <typename Tuple>
struct set_time_step<Tuple, 0> {
static void impl(Tuple& filters, double dt)
{
std::get<0>(filters).setTimestep(dt);
}
};
// For TF tests
template <typename Filter>
void test_filter(Filter& filt, const vectX_t<double>& f, const vectX_t<double>& df, double eps)
{
for (int i = 0; i < 50; ++i) {
filt.stepFilter(f(i)); // First initialize, some steps
// auto value = filt.stepFilter(f(i)); // First initialize, some steps
// value = 2.;
}
for (int i = 50; i < f.size(); ++i) {
auto value = filt.stepFilter(f(i));
REQUIRE_SMALL(std::abs(value - df(i - filt.center())), eps);
}
}
// For TV tests
template <typename Filter>
void test_filter(Filter& filt, const vectX_t<double>& t, const vectX_t<double>& f, const vectX_t<double>& df, double eps)
{
for (int i = 0; i < 50; ++i) {
filt.stepFilter(t(i), f(i)); // First initialize, some steps
// auto value = filt.stepFilter(f(i)); // First initialize, some steps
// value = 2.;
}
for (int i = 50; i < f.size(); ++i) {
auto value = filt.stepFilter(t(i), f(i));
REQUIRE_SMALL(std::abs(value - df(i - filt.center())), eps);
}
}
template <typename Tuple, size_t Index>
struct run_test {
static constexpr size_t ind = Index;
// For TF tests
static void impl(Tuple& filters, const vectX_t<double>& f, const vectX_t<double>& df, const vectX_t<double>& eps)
{
test_filter(std::get<Index>(filters), f, df, eps(ind));
run_test<Tuple, Index - 1>::impl(filters, f, df, eps);
}
// For TV tests
static void impl(Tuple& filters, const vectX_t<double>& t, const vectX_t<double>& f, const vectX_t<double>& df, const vectX_t<double>& eps)
{
test_filter(std::get<Index>(filters), t, f, df, eps(ind));
run_test<Tuple, Index - 1>::impl(filters, t, f, df, eps);
}
};
template <typename Tuple>
struct run_test<Tuple, 0> {
// For TF tests
static void impl(Tuple& filters, const vectX_t<double>& f, const vectX_t<double>& df, const vectX_t<double>& eps)
{
test_filter(std::get<0>(filters), f, df, eps(0));
}
// For TV tests
static void impl(Tuple& filters, const vectX_t<double>& t, const vectX_t<double>& f, const vectX_t<double>& df, const vectX_t<double>& eps)
{
test_filter(std::get<0>(filters), t, f, df, eps(0));
}
};
} // namespace details
template <typename Tuple>
void set_time_steps(Tuple& filters, double dt)
{
details::set_time_step<Tuple, std::tuple_size<Tuple>::value - 1>::impl(filters, dt);
}
template <typename Tuple>
void run_tests(Tuple& filters, const vectX_t<double>& f, const vectX_t<double>& df, const vectX_t<double>& eps)
{
details::run_test<Tuple, std::tuple_size<Tuple>::value - 1>::impl(filters, f, df, eps);
}
template <typename Tuple>
void run_tests(Tuple& filters, const vectX_t<double>& t, const vectX_t<double>& f, const vectX_t<double>& df, const vectX_t<double>& eps)
{
details::run_test<Tuple, std::tuple_size<Tuple>::value - 1>::impl(filters, t, f, df, eps);
}
// template <typename T, typename Tuple, typename Derived>
// class DiffTester {
// friend Derived;
// static constexpr const size_t TSize = std::tuple_size_v<Tuple>;
// public:
// DiffTester(const Tuple& filters, const vectX_t<T>& f, const vectX_t<T>& df)
// : m_filters(filters)
// , m_f(f)
// , m_df(df)
// {}
// void run(const std::array<T, TSize>& eps) { testRunner<TSize - 1>(eps); }
// private:
// template <typename Filter>
// void test(Filter& filt, T eps)
// {
// static_cast<Derived&>(*this).testFilter(filt, eps);
// }
// template <size_t Index>
// void testRunner(const std::array<T, TSize>& eps)
// {
// test(std::get<Index>(m_filters), eps[Index]);
// testRunner<Index - 1>(eps);
// }
// template <>
// void testRunner<0>(const std::array<T, TSize>& eps) { test(std::get<0>(m_filters), eps[0]); }
// private:
// vectX_t<T> m_f;
// vectX_t<T> m_df;
// Tuple m_filters;
// };
// template <typename T, typename Tuple>
// class TFDiffTester : public DiffTester<T, Tuple, TFDiffTester<T, Tuple>> {
// friend DiffTester<T, Tuple, TFDiffTester<T, Tuple>>;
// public:
// TFDiffTester(const Tuple& filters, const vectX_t<T>& f, const vectX_t<T>& df)
// : DiffTester(filters, f, df)
// {}
// void setFilterTimestep(T step) { setStep<TSize - 1>(step); }
// private:
// template <typename Filter>
// void testFilter(Filter& filt, T eps)
// {
// for (int i = 0; i < 50; ++i) {
// auto value = filt.stepFilter(m_f(i)); // First initialize, some steps
// value = 2.;
// }
// for (int i = 50; i < STEPS; ++i) {
// auto value = filt.stepFilter(m_f(i));
// REQUIRE_SMALL(std::abs(value - m_df(i - filt.center())), eps);
// }
// }
// template <size_t Index>
// void setStep(T step)
// {
// std::get<Index>(m_filters).setTimestep(step);
// setStep<Index - 1>(step);
// }
// template <>
// void setStep<0>(T step) { std::get<0>(m_filters).setTimestep(step); }
// };
// template <typename T, typename Tuple>
// class TVDiffTester : public DiffTester<T, Tuple, TVDiffTester<T, Tuple>> {
// friend DiffTester<T, Tuple, TVDiffTester<T, Tuple>>;
// public:
// TVDiffTester(const Tuple& filters, const vectX_t<T>& t, const vectX_t<T>& f, const vectX_t<T>& df)
// : DiffTester(filters, f, df)
// , m_time(t)
// {}
// private:
// template <typename Filter>
// void testFilter(Filter& filt, T eps)
// {
// for (int i = 0; i < 50; ++i) {
// auto value = filt.stepFilter(m_time(i), m_f(i)); // First initialize, some steps
// value = 2.;
// }
// for (int i = 50; i < STEPS; ++i) {
// auto value = filt.stepFilter(m_time(i), m_f(i));
// REQUIRE_SMALL(std::abs(value - m_df(i - filt.center())), eps);
// }
// }
// private:
// vectX_t<T> m_time;
// };
template <int N>
using central_list = std::tuple<CenteredDiffBasicd<N>, CenteredDiffLowNoiseLanczosd<N>, CenteredDiffSuperLowNoiseLanczosd<N>, CenteredDiffNoiseRobust2d<N>, CenteredDiffNoiseRobust4d<N>>;
// template <size_t N>
// TFDiffTester<double, central_list<N>> generateCTester(const vectX_t<double>& f, const vectX_t<double>& df)
// {
// return { central_list<N>{}, f, df };
// }
// template <size_t N>
// TFDiffTester<double, std::tuple<CenteredDiffSecondOrderd<N>>> generateC2OTester(const vectX_t<double>& f, const vectX_t<double>& df)
// {
// return { std::tuple<CenteredDiffSecondOrderd<N>>{}, f, df };
// }
template <int N>
using tv_central_list = std::tuple<TVCenteredDiffNoiseRobust2d<N>, TVCenteredDiffNoiseRobust4d<N>>;
// template <size_t N>
// TVDiffTester<double, tv_central_list<N>> generateTVCTester(const vectX_t<double>& t, const vectX_t<double>& f, const vectX_t<double>& df)
// {
// return { tv_central_list<N>{}, t, f, df };
// }
// template <size_t N>
// TVDiffTester<double, std::tuple<TVCenteredDiffSecondOrderd<N>>> generateTVC2OTester(const vectX_t<double>& t, const vectX_t<double>& f, const vectX_t<double>& df)
// {
// return { std::tuple<TVCenteredSecondOrderDiff<N>>{}, t, f, df };
// }
} // namespace tester

Wyświetl plik

@ -0,0 +1,205 @@
// Copyright (c) 2019, Vincent SAMY
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
#include "diffTesters.h"
#include "doctest/doctest.h"
#include "doctest_helper.h"
#include "noisy_function_generator.h"
#include <limits>
constexpr const int STEPS = 500;
constexpr const int SIN_AMPLITUDE = 100;
constexpr const int SIN_FREQUENCY = 1;
template <typename T> constexpr const std::array<T, 5> POLY_4 = { 2, -3, 3, 2, -5 };
template <typename T> constexpr const std::array<T, 8> POLY_7 = { 10, -12, 5, 6, -9, -3, -2, 4 };
template <int N>
vectN_t<double, N> generateLNLCoeffs()
{
static_assert(N > 2 && N % 2 == 1, "N must be odd");
vectN_t<double, N> lnl;
const int M = (N - 1) / 2;
for (int i = 0; i < M + 1; ++i) {
lnl(i) = static_cast<double>(M - i);
lnl(M + i) = -static_cast<double>(i);
}
return lnl;
}
template <int N>
void checkCoeffs(const vectN_t<double, N>& coeffs, const vectN_t<double, N>& goodCoeffs)
{
for (int i = 0; i < N; ++i)
REQUIRE_SMALL(std::abs(coeffs(i) - goodCoeffs(i)), std::numeric_limits<double>::epsilon() * 5);
}
TEST_CASE("Coefficient calculation") // Some coeffs are computed, the rest are given
{
// LNL coeffs
checkCoeffs<5>(details::GetLNLCoeffs<double, 5>{}(), generateLNLCoeffs<5>() / 10.);
checkCoeffs<7>(details::GetLNLCoeffs<double, 7>{}(), generateLNLCoeffs<7>() / 28.);
checkCoeffs<9>(details::GetLNLCoeffs<double, 9>{}(), generateLNLCoeffs<9>() / 60.);
checkCoeffs<11>(details::GetLNLCoeffs<double, 11>{}(), generateLNLCoeffs<11>() / 110.);
// FNR coeffs
checkCoeffs<3>(details::GetFNRCoeffs<double, 3>{}(), (vectN_t<double, 3>() << 1., 0., -1.).finished() / 2.);
checkCoeffs<4>(details::GetFNRCoeffs<double, 4>{}(), (vectN_t<double, 4>() << 1., 1., -1., -1.).finished() / 4.);
checkCoeffs<5>(details::GetFNRCoeffs<double, 5>{}(), (vectN_t<double, 5>() << 1., 2., 0., -2., -1.).finished() / 8.);
checkCoeffs<6>(details::GetFNRCoeffs<double, 6>{}(), (vectN_t<double, 6>() << 1., 3., 2., -2., -3., -1.).finished() / 16.);
checkCoeffs<7>(details::GetFNRCoeffs<double, 7>{}(), (vectN_t<double, 7>() << 1., 4., 5., 0., -5., -4., -1.).finished() / 32.);
checkCoeffs<8>(details::GetFNRCoeffs<double, 8>{}(), (vectN_t<double, 8>() << 1., 5., 9., 5., -5., -9., -5., -1.).finished() / 64.);
checkCoeffs<9>(details::GetFNRCoeffs<double, 9>{}(), (vectN_t<double, 9>() << 1., 6., 14., 14., 0., -14., -14., -6., -1.).finished() / 128.);
checkCoeffs<10>(details::GetFNRCoeffs<double, 10>{}(), (vectN_t<double, 10>() << 1., 7., 20., 28., 14., -14., -28., -20., -7., -1.).finished() / 256.);
checkCoeffs<11>(details::GetFNRCoeffs<double, 11>{}(), (vectN_t<double, 11>() << 1., 8., 27., 48., 42., 0., -42., -48., -27., -8., -1.).finished() / 512.);
}
TEST_CASE("Sinus time-fixed central derivative")
{
double dt = 0.001;
auto sg = sinGenerator<double>(STEPS, SIN_AMPLITUDE, SIN_FREQUENCY, dt);
auto ct7 = tester::central_list<7>{};
auto ct9 = tester::central_list<9>{};
tester::set_time_steps(ct7, dt);
tester::set_time_steps(ct9, dt);
difi::vectX_t<double> eps{ 5 };
eps << 1e-10, 1e-1, 1e-6, 1e-1, 1e-6; // Value checked with MATLAB
tester::run_tests(ct7, std::get<0>(sg), std::get<1>(sg), eps);
tester::run_tests(ct9, std::get<0>(sg), std::get<1>(sg), eps);
}
TEST_CASE("Polynome time-fixed central derivative")
{
double dt = 0.001;
{
auto pg = polyGenerator<double>(STEPS, POLY_4<double>, dt);
auto ct7 = tester::central_list<7>{};
auto ct9 = tester::central_list<9>{};
tester::set_time_steps(ct7, dt);
tester::set_time_steps(ct9, dt);
difi::vectX_t<double> eps{ 5 };
eps << 1e-12, 1e-4, 1e-12, 1e-4, 1e-12; // Value checked with MATLAB
tester::run_tests(ct7, std::get<0>(pg), std::get<1>(pg), eps);
tester::run_tests(ct9, std::get<0>(pg), std::get<1>(pg), eps);
}
{
auto pg = polyGenerator<double>(STEPS, POLY_7<double>, dt);
auto ct7 = tester::central_list<7>{};
auto ct9 = tester::central_list<9>{};
tester::set_time_steps(ct7, dt);
tester::set_time_steps(ct9, dt);
difi::vectX_t<double> eps{ 5 };
eps << 1e-11, 1e-3, 1e-9, 1e-4, 1e-9; // Value checked with MATLAB
tester::run_tests(ct7, std::get<0>(pg), std::get<1>(pg), eps);
tester::run_tests(ct9, std::get<0>(pg), std::get<1>(pg), eps);
}
}
TEST_CASE("2nd order sinus time-fixed center derivative")
{
double dt = 0.001;
auto sg = sinGenerator<double>(STEPS, SIN_AMPLITUDE, SIN_FREQUENCY, dt);
auto ct7 = std::tuple<CenteredDiffSecondOrderd<7>>{};
auto ct9 = std::tuple<CenteredDiffSecondOrderd<9>>{};
auto ct11 = std::tuple<CenteredDiffSecondOrderd<11>>{};
tester::set_time_steps(ct7, dt);
tester::set_time_steps(ct9, dt);
tester::set_time_steps(ct11, dt);
difi::vectX_t<double> eps{ 1 };
eps << 2e-1;
tester::run_tests(ct7, std::get<0>(sg), std::get<2>(sg), eps);
tester::run_tests(ct9, std::get<0>(sg), std::get<2>(sg), eps);
tester::run_tests(ct11, std::get<0>(sg), std::get<2>(sg), eps);
}
TEST_CASE("Sinus time-varying central derivative")
{
double dt = 0.001;
auto sg = tvSinGenerator<double>(STEPS, SIN_AMPLITUDE, SIN_FREQUENCY, dt);
auto ct7 = tester::tv_central_list<7>{};
auto ct9 = tester::tv_central_list<9>{};
difi::vectX_t<double> eps{ 2 };
eps << 1., 1.;
tester::run_tests(ct7, std::get<0>(sg), std::get<1>(sg), std::get<2>(sg), eps);
tester::run_tests(ct9, std::get<0>(sg), std::get<1>(sg), std::get<2>(sg), eps);
}
TEST_CASE("Polynome time-varying central derivative")
{
double dt = 0.001;
{
auto pg = tvPolyGenerator<double>(STEPS, POLY_4<double>, dt);
auto ct7 = tester::tv_central_list<7>{};
auto ct9 = tester::tv_central_list<9>{};
difi::vectX_t<double> eps{ 2 };
eps << 1e-3, 1e-3;
tester::run_tests(ct7, std::get<0>(pg), std::get<1>(pg), std::get<2>(pg), eps);
tester::run_tests(ct9, std::get<0>(pg), std::get<1>(pg), std::get<2>(pg), eps);
}
{
auto pg = tvPolyGenerator<double>(STEPS, POLY_7<double>, dt);
auto ct7 = tester::tv_central_list<7>{};
auto ct9 = tester::tv_central_list<9>{};
difi::vectX_t<double> eps{ 2 };
eps << 1e-2, 1e-2;
tester::run_tests(ct7, std::get<0>(pg), std::get<1>(pg), std::get<2>(pg), eps);
tester::run_tests(ct9, std::get<0>(pg), std::get<1>(pg), std::get<2>(pg), eps);
}
}
// TEST_CASE("2nd order sinus time-varying center derivative", "[tv][sin][center][2nd]")
// {
// // Test not passing.
// double dt = 0.001;
// auto sg = tvSinGenerator<double>(STEPS, SIN_AMPLITUDE, SIN_FREQUENCY, dt);
// auto ct7 = generateTVC2OTester<double, 7>(std::get<0>(sg), std::get<1>(sg), std::get<3>(sg));
// auto ct9 = generateTVC2OTester<double, 9>(std::get<0>(sg), std::get<1>(sg), std::get<3>(sg));
// auto ct11 = generateTVC2OTester<double, 11>(std::get<0>(sg), std::get<1>(sg), std::get<3>(sg));
// std::array<double, 1> eps = { 2e-1 };
// ct7.run(eps);
// ct9.run(eps);
// ct11.run(eps);
// }

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1,4 @@
#pragma once
#define REQUIRE_EQUAL(left, right) REQUIRE((left) == (right))
#define REQUIRE_SMALL(value, eps) REQUIRE((value) < (eps))

Wyświetl plik

@ -0,0 +1,197 @@
// Copyright (c) 2019, Vincent SAMY
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
#pragma once
#include "typedefs.h"
#include <array>
#include <cmath>
#include <tuple>
template <typename T>
using FunctionGenerator = std::tuple<difi::vectX_t<T>, difi::vectX_t<T>, difi::vectX_t<T>>;
template <typename T>
FunctionGenerator<T> sinGenerator(int nrSteps, T amplitude, T frequency, T dt)
{
using namespace difi;
vectX_t<T> f(nrSteps);
vectX_t<T> df(nrSteps);
vectX_t<T> ddf(nrSteps);
for (int i = 0; i < nrSteps; ++i) {
// function
f(i) = amplitude * std::sin(2 * pi<T> * frequency * i * dt);
// derivative 1
df(i) = 2 * pi<T> * frequency * amplitude * std::cos(2 * pi<T> * frequency * i * dt);
// derivative 2
ddf(i) = -4 * pi<T> * pi<T> * frequency * frequency * amplitude * std::sin(2 * pi<T> * frequency * i * dt);
}
return { f, df, ddf };
}
template <typename T, size_t N>
FunctionGenerator<T> polyGenerator(int nrSteps, std::array<T, N> coeffs, T dt)
{
using namespace difi;
static_assert(N >= 2, "N must be superior to 20");
auto computePoly = [](const auto& coeffs, T time) {
auto recursiveComputation = [time, &coeffs](size_t i, T result, const auto& self) -> T {
if (i > 0)
return self(i - 1, time * result + coeffs[i - 1], self);
else
return result;
};
return recursiveComputation(coeffs.size(), 0, recursiveComputation);
};
std::array<T, N - 1> dCoeffs;
for (size_t i = 1; i < N; ++i)
dCoeffs[i - 1] = static_cast<T>(i) * coeffs[i];
std::array<T, N - 2> ddCoeffs;
for (size_t i = 1; i < N - 1; ++i)
ddCoeffs[i - 1] = static_cast<T>(i) * dCoeffs[i];
vectX_t<T> f(nrSteps);
vectX_t<T> df(nrSteps);
vectX_t<T> ddf(nrSteps);
for (int i = 0; i < nrSteps; ++i) {
// function
f(i) = computePoly(coeffs, i * dt);
// derivative 1
df(i) = computePoly(dCoeffs, i * dt);
// derivative 2
ddf(i) = computePoly(ddCoeffs, i * dt);
}
return { f, df, ddf };
}
// TV sin generator
template <typename T>
using TVFunctionGenerator = std::tuple<difi::vectX_t<T>, difi::vectX_t<T>, difi::vectX_t<T>, difi::vectX_t<T>>;
template <typename T>
TVFunctionGenerator<T> tvSinGenerator(int nrSteps, T amplitude, T frequency, T meanDt)
{
using namespace difi;
vectX_t<T> t(nrSteps);
vectX_t<T> f(nrSteps);
vectX_t<T> df(nrSteps);
vectX_t<T> ddf(nrSteps);
t(0) = T(0);
f(0) = T(0);
df(0) = 2 * pi<T> * frequency * amplitude;
ddf(0) = T(0);
for (int i = 1; i < nrSteps; ++i) {
// time
if (i % 3 == 0)
t(i) = t(i - 1) + 0.9 * meanDt;
else if (i % 3 == 1)
t(i) = t(i - 1) + meanDt;
else
t(i) = t(i - 1) + 1.1 * meanDt;
// function
f(i) = amplitude * std::sin(2 * pi<T> * frequency * t(i));
// derivative 1
df(i) = 2 * pi<T> * frequency * amplitude * std::cos(2 * pi<T> * frequency * t(i));
// derivative 2
ddf(i) = -4 * pi<T> * pi<T> * frequency * frequency * amplitude * std::sin(2 * pi<T> * frequency * t(i));
}
return { t, f, df, ddf };
}
template <typename T, size_t N>
TVFunctionGenerator<T> tvPolyGenerator(int nrSteps, std::array<T, N> coeffs, T meanDt)
{
using namespace difi;
static_assert(N >= 2, "N must be superior to 20");
auto computePoly = [](const auto& coeffs, T time) {
auto recursiveComputation = [time, &coeffs](size_t i, T result, const auto& self) -> T {
if (i > 0)
return self(i - 1, time * result + coeffs[i - 1], self);
else
return result;
};
return recursiveComputation(coeffs.size(), 0, recursiveComputation);
};
std::array<T, N - 1> dCoeffs;
for (size_t i = 1; i < N; ++i)
dCoeffs[i - 1] = static_cast<T>(i) * coeffs[i];
std::array<T, N - 2> ddCoeffs;
for (size_t i = 1; i < N - 1; ++i)
ddCoeffs[i - 1] = static_cast<T>(i) * dCoeffs[i];
vectX_t<T> t(nrSteps);
vectX_t<T> f(nrSteps);
vectX_t<T> df(nrSteps);
vectX_t<T> ddf(nrSteps);
t(0) = T(0);
f(0) = computePoly(coeffs, t(0));
df(0) = computePoly(dCoeffs, t(0));
ddf(0) = computePoly(ddCoeffs, t(0));
for (int i = 1; i < nrSteps; ++i) {
// time
if (i % 3 == 0)
t(i) = t(i - 1) + 0.9 * meanDt;
else if (i % 3 == 1)
t(i) = t(i - 1) + meanDt;
else
t(i) = t(i - 1) + 1.1 * meanDt;
// function
f(i) = computePoly(coeffs, t(i));
// derivative 1
df(i) = computePoly(dCoeffs, t(i));
// derivative 2
ddf(i) = computePoly(ddCoeffs, t(i));
}
return { t, f, df, ddf };
}

Wyświetl plik

@ -25,11 +25,10 @@
// of the authors and should not be interpreted as representing official policies, // of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project. // either expressed or implied, of the FreeBSD Project.
#define BOOST_TEST_MODULE polynome_functions_tests
#include "difi" #include "difi"
#include "doctest/doctest.h"
#include "doctest_helper.h"
#include "warning_macro.h" #include "warning_macro.h"
#include <boost/test/unit_test.hpp>
#include <limits> #include <limits>
using c_int_t = std::complex<int>; using c_int_t = std::complex<int>;
@ -62,50 +61,36 @@ struct SystemCFloat {
DISABLE_CONVERSION_WARNING_END DISABLE_CONVERSION_WARNING_END
BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_INT, SystemInt) TEST_CASE("Polynome function for int")
{ {
auto res = difi::VietaAlgoi::polyCoeffFromRoot(data); SystemInt s;
auto res = difi::VietaAlgoi::polyCoeffFromRoot(s.data);
for (Eigen::Index i = 0; i < res.size(); ++i) for (Eigen::Index i = 0; i < res.size(); ++i)
BOOST_REQUIRE_EQUAL(res(i), results(i)); REQUIRE_EQUAL(res(i), s.results(i));
} }
BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_FLOAT, SystemFloat<float>) TEST_CASE_TEMPLATE("Polynome function for floating point", T, float, double)
{ {
auto res = difi::VietaAlgof::polyCoeffFromRoot(data); SystemFloat<T> s;
auto res = difi::VietaAlgo<T>::polyCoeffFromRoot(s.data);
for (Eigen::Index i = 0; i < res.size(); ++i) for (Eigen::Index i = 0; i < res.size(); ++i)
BOOST_REQUIRE_SMALL(std::abs(res(i) - results(i)), std::numeric_limits<float>::epsilon() * 1000); REQUIRE_SMALL(std::abs(res(i) - s.results(i)), std::numeric_limits<T>::epsilon() * 1000);
} }
BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_DOUBLE, SystemFloat<double>) TEST_CASE("Polynome function for complex int")
{ {
auto res = difi::VietaAlgod::polyCoeffFromRoot(data); SystemCInt s;
auto res = difi::VietaAlgoci::polyCoeffFromRoot(s.data);
for (Eigen::Index i = 0; i < res.size(); ++i)
REQUIRE_EQUAL(res(i), s.results(i));
}
TEST_CASE_TEMPLATE("Polynome function for complex floating point", T, float, double)
{
SystemCFloat<T> s;
auto res = difi::VietaAlgo<std::complex<T>>::polyCoeffFromRoot(s.data);
for (Eigen::Index i = 0; i < res.size(); ++i) for (Eigen::Index i = 0; i < res.size(); ++i)
BOOST_REQUIRE_SMALL(std::abs(res(i) - results(i)), std::numeric_limits<double>::epsilon() * 1000); REQUIRE_SMALL(std::abs(res(i) - s.results(i)), std::numeric_limits<T>::epsilon() * 1000);
}
BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_CINT, SystemCInt)
{
auto res = difi::VietaAlgoci::polyCoeffFromRoot(data);
for (Eigen::Index i = 0; i < res.size(); ++i)
BOOST_REQUIRE_EQUAL(res(i), results(i));
}
BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_CFLOAT, SystemCFloat<float>)
{
auto res = difi::VietaAlgocf::polyCoeffFromRoot(data);
for (Eigen::Index i = 0; i < res.size(); ++i)
BOOST_REQUIRE_SMALL(std::abs(res(i) - results(i)), std::numeric_limits<float>::epsilon() * 1000);
}
BOOST_FIXTURE_TEST_CASE(POLYNOME_FUNCTION_CDOUBLE, SystemCFloat<double>)
{
auto res = difi::VietaAlgocd::polyCoeffFromRoot(data);
for (Eigen::Index i = 0; i < res.size(); ++i)
BOOST_REQUIRE_SMALL(std::abs(res(i) - results(i)), std::numeric_limits<double>::epsilon() * 1000);
} }

Wyświetl plik

@ -28,20 +28,21 @@
#pragma once #pragma once
#include "difi" #include "difi"
#include <boost/test/unit_test.hpp> #include "doctest/doctest.h"
#include "doctest_helper.h"
#include <limits> #include <limits>
template <typename T> template <typename T>
void test_coeffs(const difi::vectX_t<T>& aCoeff, const difi::vectX_t<T>& bCoeff, const difi::GenericFilter<T>& filter, T prec) void test_coeffs(const difi::vectX_t<T>& aCoeff, const difi::vectX_t<T>& bCoeff, const difi::GenericFilter<T>& filter, T prec)
{ {
BOOST_REQUIRE_EQUAL(aCoeff.size(), filter.aOrder()); REQUIRE_EQUAL(aCoeff.size(), filter.aOrder());
BOOST_REQUIRE_EQUAL(bCoeff.size(), filter.bOrder()); REQUIRE_EQUAL(bCoeff.size(), filter.bOrder());
difi::vectX_t<T> faCoeff, fbCoeff; difi::vectX_t<T> faCoeff, fbCoeff;
filter.getCoeffs(faCoeff, fbCoeff); filter.getCoeffs(faCoeff, fbCoeff);
for (Eigen::Index i = 0; i < faCoeff.size(); ++i) for (Eigen::Index i = 0; i < faCoeff.size(); ++i)
BOOST_REQUIRE_SMALL(std::abs(aCoeff(i) - faCoeff(i)), prec); REQUIRE_SMALL(std::abs(aCoeff(i) - faCoeff(i)), prec);
for (Eigen::Index i = 0; i < fbCoeff.size(); ++i) for (Eigen::Index i = 0; i < fbCoeff.size(); ++i)
BOOST_REQUIRE_SMALL(std::abs(bCoeff(i) - fbCoeff(i)), prec); REQUIRE_SMALL(std::abs(bCoeff(i) - fbCoeff(i)), prec);
} }
template <typename T> template <typename T>
@ -53,10 +54,10 @@ void test_results(const difi::vectX_t<T>& results, const difi::vectX_t<T>& data,
filteredData(i) = filter.stepFilter(data(i)); filteredData(i) = filter.stepFilter(data(i));
for (Eigen::Index i = 0; i < filteredData.size(); ++i) for (Eigen::Index i = 0; i < filteredData.size(); ++i)
BOOST_REQUIRE_SMALL(std::abs(filteredData(i) - results(i)), prec); REQUIRE_SMALL(std::abs(filteredData(i) - results(i)), prec);
filter.resetFilter(); filter.resetFilter();
filteredData = filter.filter(data); filteredData = filter.filter(data);
for (Eigen::Index i = 0; i < filteredData.size(); ++i) for (Eigen::Index i = 0; i < filteredData.size(); ++i)
BOOST_REQUIRE_SMALL(std::abs(filteredData(i) - results(i)), prec); REQUIRE_SMALL(std::abs(filteredData(i) - results(i)), prec);
} }

Wyświetl plik

@ -27,18 +27,17 @@
#pragma once #pragma once
#ifdef _MSC_VER #if defined(_MSC_VER)
#define DCW_BEGIN \ #define DCW_BEGIN \
__pragma(warning(push)) \ __pragma(warning(push)) \
__pragma(warning(disable : 4305)) __pragma(warning(disable : 4305))
#define DCW_END __pragma(warning(pop)) #define DCW_END __pragma(warning(pop))
#else #elif defined(__GNUC__) || defined(__clang__)
#ifdef __GNUC__ || __clang__
#define DCW_BEGIN \ #define DCW_BEGIN \
_Pragma("GCC warning push") \ _Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wconversion\"") _Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"") \
#define DCW_END _Pragma("GCC warning pop") _Pragma("GCC diagnostic ignored \"-Wconversion\"") // Only needed for clang
#endif #define DCW_END _Pragma("GCC diagnostic pop")
#endif #endif
#ifdef DCW_BEGIN #ifdef DCW_BEGIN