diff --git a/README.md b/README.md index bd08c3b9..b08d4d71 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Refer to the documentation to quickly integrate and utilize the library's signal | [Solvers](doc/solvers/README.md) | Gaussian Elimination, Levinson-Durbin, Durand-Kerner, Cholesky, DARE, Runge-Kutta ODE Integrators (RK4 + Dormand-Prince), Spectral Radius & Discrete Stability Margin, QR Decomposition (Householder / Givens), LU Decomposition with Partial Pivoting, Singular Value Decomposition (Golub-Kahan) | | [Nonlinear Control](doc/nonlinear_control/README.md) | Feedback Linearization, Backstepping Control, Model Reference Adaptive Control (MRAC) | | [Robust Control](doc/robust_control/README.md) | Active Disturbance Rejection Control (ADRC + ESO), Sliding Mode Control (SMC), Disturbance Observer (DOB), H∞ State-Feedback Control | -| [Performance Optimization](doc/performance-optimization/README.md) | Compiler optimizations, SIMD | +| [Performance Optimization](doc/performance-optimization/README.md) | Compiler optimizations | Each category page lists its algorithms with a brief description and links to the detailed documentation. diff --git a/doc/optimization/Optimizer.md b/doc/optimization/Optimizer.md index ff3557e5..ba834de1 100644 --- a/doc/optimization/Optimizer.md +++ b/doc/optimization/Optimizer.md @@ -2,7 +2,7 @@ ## Overview & Motivation -An **optimizer** adjusts a model's parameter vector $\theta \in \mathbb{R}^P$ to minimize a [loss function](../losses/Loss.md) $\mathcal{L}(\theta)$. The simplest and most fundamental strategy is **gradient descent**: repeatedly step in the direction of steepest descent: +An **optimizer** adjusts a model's parameter vector $\theta \in \mathbb{R}^P$ to minimize a loss function $\mathcal{L}(\theta)$. The simplest and most fundamental strategy is **gradient descent**: repeatedly step in the direction of steepest descent: $$\theta_{t+1} = \theta_t - \eta \, \nabla_\theta \mathcal{L}(\theta_t)$$ @@ -14,7 +14,7 @@ This library provides a batch gradient descent optimizer with a fixed learning r ### The Gradient -The gradient $\nabla_\theta \mathcal{L}$ is obtained via [back-propagation](../NeuralNetwork.md). Each component $\frac{\partial \mathcal{L}}{\partial \theta_i}$ tells how much the loss changes when $\theta_i$ is perturbed by a small amount. The gradient points **uphill**; subtracting it moves **downhill**. +The gradient $\nabla_\theta \mathcal{L}$ is obtained via back-propagation. Each component $\frac{\partial \mathcal{L}}{\partial \theta_i}$ tells how much the loss changes when $\theta_i$ is perturbed by a small amount. The gradient points **uphill**; subtracting it moves **downhill**. ### Convergence Conditions @@ -127,12 +127,12 @@ graph TD Opt -.->|"analytical solution at η→∞, 1 step"| LR ``` -| Component | Relationship | -|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------| -| [Loss Functions](../losses/Loss.md) | Provides the `Cost()` and `Gradient()` the optimizer calls each iteration | -| [Model](../model/Model.md) | Passes initial parameters to the optimizer and receives optimized parameters back | -| [Regularization](../regularization/Regularization.md) | Adds a penalty gradient to $\nabla\mathcal{L}$, biasing the optimizer toward simpler models | -| [Linear Regression](../../estimators/LinearRegression.md) | For MSE on a linear model, gradient descent converges to the same solution as the normal equation | +| Component | Relationship | +|--------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| Loss Functions | Provides the `Cost()` and `Gradient()` the optimizer calls each iteration | +| Model | Passes initial parameters to the optimizer and receives optimized parameters back | +| [Regularization](../regularization/Regularization.md) | Adds a penalty gradient to $\nabla\mathcal{L}$, biasing the optimizer toward simpler models | +| [Linear Regression](../estimators/LinearRegression.md) | For MSE on a linear model, gradient descent converges to the same solution as the normal equation | ## References & Further Reading diff --git a/doc/regularization/Regularization.md b/doc/regularization/Regularization.md index 63cf8431..6747c47c 100644 --- a/doc/regularization/Regularization.md +++ b/doc/regularization/Regularization.md @@ -122,11 +122,11 @@ graph TD Reg -.->|"L2 + MSE = Ridge regression"| LR ``` -| Component | Relationship | -|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| -| [Loss Functions](../losses/Loss.md) | Regularization is a penalty *added to* the loss: $\mathcal{L}_{\text{total}} = \mathcal{L} + \lambda\Omega$ | -| [Optimizer](../optimizer/Optimizer.md) | Receives the combined gradient $\nabla\mathcal{L} + \lambda\nabla\Omega$ | -| [Linear Regression](../../estimators/LinearRegression.md) | L2-regularized MSE with a linear model is **Ridge regression**; L1 is **Lasso** | +| Component | Relationship | +|--------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| +| Loss Functions | Regularization is a penalty *added to* the loss: $\mathcal{L}_{\text{total}} = \mathcal{L} + \lambda\Omega$ | +| [Optimizer](../optimization/Optimizer.md) | Receives the combined gradient $\nabla\mathcal{L} + \lambda\nabla\Omega$ | +| [Linear Regression](../estimators/LinearRegression.md) | L2-regularized MSE with a linear model is **Ridge regression**; L1 is **Lasso** | ## References & Further Reading diff --git a/numerical/analysis/FastFourierTransform.hpp b/numerical/analysis/FastFourierTransform.hpp index fd8de657..6d163c26 100644 --- a/numerical/analysis/FastFourierTransform.hpp +++ b/numerical/analysis/FastFourierTransform.hpp @@ -9,6 +9,7 @@ namespace analysis class TwiddleFactors { public: + virtual ~TwiddleFactors() = default; virtual math::Complex& operator[](std::size_t n) = 0; }; @@ -20,9 +21,17 @@ namespace analysis "FastFourierTransform can only be instantiated with math::QNumber types."); public: - constexpr std::size_t Log2(std::size_t n) + virtual ~FastFourierTransform() = default; + + static constexpr std::size_t Log2(std::size_t n) { - return (n <= 1) ? 0 : 1 + Log2(n >> 1); + std::size_t result = 0; + while (n > 1) + { + n >>= 1; + ++result; + } + return result; } using VectorComplex = infra::BoundedVector>; diff --git a/numerical/analysis/test/TestFastFourierTransformRadix2Impl.cpp b/numerical/analysis/test/TestFastFourierTransformRadix2Impl.cpp index a428ecc3..378df07c 100644 --- a/numerical/analysis/test/TestFastFourierTransformRadix2Impl.cpp +++ b/numerical/analysis/test/TestFastFourierTransformRadix2Impl.cpp @@ -99,13 +99,35 @@ namespace typename VectorReal::template WithMaxSize timeDomain; typename VectorComplex::template WithMaxSize frequencyDomain; }; + + class MockTwiddleFactorsDegenerate + : public analysis::TwiddleFactors + { + public: + math::Complex& operator[](std::size_t) override + { + return dummy; + } + + private: + math::Complex dummy{}; + }; + + class TestFastFourierTransformCoverage : public ::testing::Test + {}; + + class TestFastFourierTransformSingleSample : public ::testing::Test + { + protected: + MockTwiddleFactorsDegenerate tw; + analysis::FastFourierTransformRadix2Impl fft{ tw }; + }; } TYPED_TEST(TestFastFourierTransform, log2_runtime_both_branches) { - auto& fftInst = *this->fft; - EXPECT_EQ(fftInst.Log2(1), 0u); - EXPECT_EQ(fftInst.Log2(8), 3u); + EXPECT_EQ(analysis::FastFourierTransform::Log2(1), 0u); + EXPECT_EQ(analysis::FastFourierTransform::Log2(8), 3u); } TYPED_TEST(TestFastFourierTransform, zero_input_produces_zero_output) @@ -302,3 +324,37 @@ TEST_F(TestFastFourierTransformFloat, all_bins_match_direct_dft) EXPECT_NEAR(result[k].Imaginary(), refImag, 1e-3f); } } + +TEST_F(TestFastFourierTransformCoverage, twiddle_factors_virtual_destructor) +{ + MockTwiddleFactors tf; + (void)tf; +} + +TEST_F(TestFastFourierTransformCoverage, fft_base_virtual_destructor) +{ + MockTwiddleFactors tw; + analysis::FastFourierTransformRadix2Impl fft{ tw }; + (void)fft; +} + +TEST_F(TestFastFourierTransformCoverage, log2_static_one_returns_zero) +{ + EXPECT_EQ(analysis::FastFourierTransform::Log2(1), 0u); +} + +TEST_F(TestFastFourierTransformCoverage, log2_static_eight_returns_three) +{ + EXPECT_EQ(analysis::FastFourierTransform::Log2(8), 3u); +} + +TEST_F(TestFastFourierTransformSingleSample, forward_single_sample_is_identity) +{ + typename analysis::FastFourierTransform::VectorReal::template WithMaxSize<1> input; + input.push_back(0.5f); + + auto& result = fft.Forward(input); + + EXPECT_NEAR(result[0].Real(), 0.5f, 1e-5f); + EXPECT_NEAR(result[0].Imaginary(), 0.0f, 1e-5f); +} diff --git a/numerical/controllers/interfaces/PidDriver.hpp b/numerical/controllers/interfaces/PidDriver.hpp index 54a51181..f58bd11c 100644 --- a/numerical/controllers/interfaces/PidDriver.hpp +++ b/numerical/controllers/interfaces/PidDriver.hpp @@ -9,6 +9,7 @@ namespace controllers class PidDriver { public: + virtual ~PidDriver() = default; virtual void Read(const infra::Function& onDone) = 0; virtual void ControlAction(QNumberType) = 0; virtual void Start(std::chrono::system_clock::duration sampleTime) = 0; diff --git a/numerical/estimators/Estimator.hpp b/numerical/estimators/Estimator.hpp index 03354741..77a8dd61 100644 --- a/numerical/estimators/Estimator.hpp +++ b/numerical/estimators/Estimator.hpp @@ -11,6 +11,8 @@ namespace estimators "OfflineEstimator only supports float or QNumber types"); public: + virtual ~OfflineEstimator() = default; + using CoefficientsMatrix = math::Matrix; using DesignMatrix = math::Matrix; using InputMatrix = math::Matrix; @@ -27,6 +29,8 @@ namespace estimators "OnlineEstimator only supports float or QNumber types"); public: + virtual ~OnlineEstimator() = default; + using CoefficientsMatrix = math::Matrix; using DesignMatrix = math::Matrix; using InputMatrix = math::Matrix; diff --git a/numerical/math/test/TestQNumber.cpp b/numerical/math/test/TestQNumber.cpp index d65f627b..ed921ba0 100644 --- a/numerical/math/test/TestQNumber.cpp +++ b/numerical/math/test/TestQNumber.cpp @@ -15,6 +15,9 @@ namespace using QNumberTypes = ::testing::Types; TYPED_TEST_SUITE(QNumberTest, QNumberTypes); + + class QNumberUtilTest : public ::testing::Test + {}; } TYPED_TEST(QNumberTest, DefaultConstructorIsZero) @@ -324,27 +327,27 @@ TYPED_TEST(QNumberTest, ToFloatFreeFunction_QNumber) EXPECT_NEAR(math::ToFloat(a), 0.25f, math::Tolerance()); } -TEST(QNumberUtilTest, ToFloatFreeFunction_Float) +TEST_F(QNumberUtilTest, ToFloatFreeFunction_Float) { EXPECT_FLOAT_EQ(math::ToFloat(0.5f), 0.5f); EXPECT_FLOAT_EQ(math::ToFloat(-0.3f), -0.3f); } -TEST(QNumberUtilTest, MinMaxLowest_Float) +TEST_F(QNumberUtilTest, MinMaxLowest_Float) { EXPECT_GT(math::Min(), 0.0f); EXPECT_GT(math::Max(), 1.0f); EXPECT_LT(math::Lowest(), 0.0f); } -TEST(QNumberUtilTest, MinMaxLowest_Q31) +TEST_F(QNumberUtilTest, MinMaxLowest_Q31) { EXPECT_NEAR(math::Min(), -0.9999f, 1e-3f); EXPECT_NEAR(math::Max(), 0.9999f, 1e-3f); EXPECT_NEAR(math::Lowest(), -0.9999f, 1e-3f); } -TEST(QNumberUtilTest, MinMaxLowest_Q15) +TEST_F(QNumberUtilTest, MinMaxLowest_Q15) { EXPECT_NEAR(math::Min(), -0.9999f, 1e-3f); EXPECT_NEAR(math::Max(), 0.9999f, 1e-3f); diff --git a/numerical/math/test_doubles/CMakeLists.txt b/numerical/math/test_doubles/CMakeLists.txt index 843420f9..5b91e140 100644 --- a/numerical/math/test_doubles/CMakeLists.txt +++ b/numerical/math/test_doubles/CMakeLists.txt @@ -8,5 +8,4 @@ target_link_libraries(numerical.math_test_helper INTERFACE target_sources(numerical.math_test_helper PRIVATE MatrixTestSupport.hpp - SingleInstructionMultipleDataStub.hpp ) diff --git a/numerical/math/test_doubles/SingleInstructionMultipleDataStub.hpp b/numerical/math/test_doubles/SingleInstructionMultipleDataStub.hpp deleted file mode 100644 index fabaa58b..00000000 --- a/numerical/math/test_doubles/SingleInstructionMultipleDataStub.hpp +++ /dev/null @@ -1,135 +0,0 @@ -#pragma once -#include "numerical/math/SingleInstructionMultipleData.hpp" - -namespace math -{ - class SingleInstructionMultipltDataStub - : public SingleInstructionMultipltData - { - public: - uint32_t Pkhbt(uint32_t value1, uint32_t value2, uint32_t value3) override - { - return 0; - } - - uint32_t Pkhtb(uint32_t value1, uint32_t value2, uint32_t value3) override - { - return 0; - } - - uint32_t Qadd(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Qadd16(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Qadd8(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Qasx(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Qsax(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Qsub(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Qsub16(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Qsub8(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Shadd16(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Shasx(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Shsax(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Shsub16(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Smlad(uint32_t value1, uint32_t value2, uint32_t value3) override - { - return 0; - } - - uint32_t Smladx(uint32_t value1, uint32_t value2, uint32_t value3) override - { - return 0; - } - - uint64_t Smlald(uint32_t value1, uint32_t value2, uint64_t value3) override - { - return 0; - } - - unsigned long long Smlaldx(uint32_t value1, uint32_t value2, unsigned long long value3) override - { - return 0; - } - - uint32_t Smlsdx(uint32_t value1, uint32_t value2, uint32_t value3) override - { - return 0; - } - - uint32_t Smmla(int32_t value1, int32_t value2, int32_t value3) override - { - return 0; - } - - uint32_t Smuad(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Smuadx(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Smusd(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Smusdx(uint32_t value1, uint32_t value2) override - { - return 0; - } - - uint32_t Sxtb16(uint32_t value) override - { - return 0; - } - }; -} diff --git a/numerical/optimization/Optimizer.hpp b/numerical/optimization/Optimizer.hpp index 525b3f84..3e87d09d 100644 --- a/numerical/optimization/Optimizer.hpp +++ b/numerical/optimization/Optimizer.hpp @@ -12,6 +12,8 @@ namespace optimization "Optimizer can only be instantiated with math::QNumber types."); public: + virtual ~Optimizer() = default; + using Vector = math::Vector; struct Result diff --git a/numerical/solvers/Solver.hpp b/numerical/solvers/Solver.hpp index 7bdf63a9..41c7c18e 100644 --- a/numerical/solvers/Solver.hpp +++ b/numerical/solvers/Solver.hpp @@ -13,6 +13,8 @@ namespace solvers "Solver dimensions must be positive"); public: + virtual ~Solver() = default; + using SolutionVector = math::Vector; using InputMatrix = math::Matrix; using InputVector = math::Vector;