Restored recommendations based on acoustic similarities (using musicnn), fixes #301
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
add_library(lmsmath INTERFACE)
|
||||
|
||||
target_include_directories(lmsmath INTERFACE
|
||||
include
|
||||
)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
if (BUILD_BENCHMARKS)
|
||||
add_subdirectory(bench)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
add_executable(bench-math
|
||||
ChamferDistance.cpp
|
||||
CosineDistance.cpp
|
||||
DotProduct.cpp
|
||||
EuclideanDistance.cpp
|
||||
FFT.cpp
|
||||
Math.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(bench-math PRIVATE
|
||||
lmscore
|
||||
lmsmath
|
||||
benchmark
|
||||
)
|
||||
|
||||
target_compile_options(bench-math PRIVATE
|
||||
$<$<NOT:$<CONFIG:Debug>>:-ffast-math>
|
||||
)
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "core/Random.hpp"
|
||||
|
||||
#include "math/ChamferDistance.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::core::benchs
|
||||
{
|
||||
template<std::size_t Size>
|
||||
struct BenchDistance
|
||||
{
|
||||
BenchDistance(const math::Vector<Size, float>& ref)
|
||||
: _ref{ ref } {}
|
||||
|
||||
float operator()(const math::Vector<Size, float>& b) const
|
||||
{
|
||||
float sum{};
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
const float diff{ _ref[i] - b[i] };
|
||||
sum += diff * diff;
|
||||
}
|
||||
return std::sqrt(sum);
|
||||
}
|
||||
|
||||
const math::Vector<Size, float>& _ref;
|
||||
};
|
||||
|
||||
template<std::size_t VectorSize, std::size_t SetASize, std::size_t SetBSize>
|
||||
static void BM_ChamferDistanceAtoB(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand randomEngine{ 0 };
|
||||
|
||||
std::vector<math::Vector<VectorSize, float>> vecA;
|
||||
std::vector<math::Vector<VectorSize, float>> vecB;
|
||||
vecA.reserve(SetASize);
|
||||
vecB.reserve(SetBSize);
|
||||
|
||||
for (std::size_t i{}; i < SetASize; ++i)
|
||||
{
|
||||
auto& vec{ vecA.emplace_back() };
|
||||
core::random::fillContainer(randomEngine, vec, 0.F, 1.F);
|
||||
}
|
||||
|
||||
for (std::size_t i{}; i < SetBSize; ++i)
|
||||
{
|
||||
auto& vec{ vecB.emplace_back() };
|
||||
core::random::fillContainer(randomEngine, vec, 0.F, 1.F);
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(math::chamferDistanceAtoB<BenchDistance<VectorSize>>(vecA, vecB));
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * SetASize * SetBSize);
|
||||
}
|
||||
|
||||
template<std::size_t VectorSize, std::size_t SetASize, std::size_t SetBSize>
|
||||
static void BM_SymmetricalChamferDistance(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand randomEngine{ 0 };
|
||||
|
||||
std::vector<math::Vector<VectorSize, float>> vecA;
|
||||
std::vector<math::Vector<VectorSize, float>> vecB;
|
||||
vecA.reserve(SetASize);
|
||||
vecB.reserve(SetBSize);
|
||||
|
||||
for (std::size_t i{}; i < SetASize; ++i)
|
||||
{
|
||||
auto& vec{ vecA.emplace_back() };
|
||||
core::random::fillContainer(randomEngine, vec, 0.F, 1.F);
|
||||
}
|
||||
|
||||
for (std::size_t i{}; i < SetBSize; ++i)
|
||||
{
|
||||
auto& vec{ vecB.emplace_back() };
|
||||
core::random::fillContainer(randomEngine, vec, 0.F, 1.F);
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(math::symmetricalChamferDistance<BenchDistance<VectorSize>>(vecA, vecB));
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * 2 * SetASize * SetBSize);
|
||||
}
|
||||
|
||||
// Benchmarks with different configurations
|
||||
BENCHMARK_TEMPLATE(BM_ChamferDistanceAtoB, 128, 10, 10);
|
||||
BENCHMARK_TEMPLATE(BM_ChamferDistanceAtoB, 128, 50, 50);
|
||||
BENCHMARK_TEMPLATE(BM_ChamferDistanceAtoB, 256, 10, 10);
|
||||
BENCHMARK_TEMPLATE(BM_ChamferDistanceAtoB, 256, 50, 50);
|
||||
|
||||
BENCHMARK_TEMPLATE(BM_SymmetricalChamferDistance, 128, 10, 10);
|
||||
BENCHMARK_TEMPLATE(BM_SymmetricalChamferDistance, 128, 50, 50);
|
||||
BENCHMARK_TEMPLATE(BM_SymmetricalChamferDistance, 256, 10, 10);
|
||||
BENCHMARK_TEMPLATE(BM_SymmetricalChamferDistance, 256, 50, 50);
|
||||
} // namespace lms::core::benchs
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <random>
|
||||
|
||||
#include "core/Random.hpp"
|
||||
|
||||
#include "math/CosineDistance.hpp"
|
||||
#include "math/NormalizedCosineDistance.hpp"
|
||||
|
||||
namespace lms::math::benchs
|
||||
{
|
||||
template<std::size_t Size>
|
||||
static void BM_CosineDistance(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand randomEngine{ 0 };
|
||||
|
||||
Vector<Size, float> vec1;
|
||||
Vector<Size, float> vec2;
|
||||
|
||||
core::random::fillContainer(randomEngine, vec1, 0.F, 1.F);
|
||||
core::random::fillContainer(randomEngine, vec2, 0.F, 1.F);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(computeCosineDistance(vec1, vec2));
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * Size);
|
||||
}
|
||||
|
||||
template<std::size_t Size>
|
||||
static void BM_NormalizedCosineDistance(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand randomEngine{ 0 };
|
||||
|
||||
Vector<Size, float> vec1;
|
||||
Vector<Size, float> vec2;
|
||||
|
||||
core::random::fillContainer(randomEngine, vec1, 0.F, 1.F);
|
||||
core::random::fillContainer(randomEngine, vec2, 0.F, 1.F);
|
||||
|
||||
// normalize once, the normalized distance assumes L2-normalized vectors
|
||||
vec1.normalizeL2();
|
||||
vec2.normalizeL2();
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(computeNormalizedCosineDistance(vec1, vec2));
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * Size);
|
||||
}
|
||||
|
||||
template<std::size_t Size>
|
||||
static void BM_NormalizedCosineDistance_Functor(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand randomEngine{ 0 };
|
||||
|
||||
Vector<Size, float> vec1;
|
||||
Vector<Size, float> vec2;
|
||||
|
||||
core::random::fillContainer(randomEngine, vec1, 0.F, 1.F);
|
||||
core::random::fillContainer(randomEngine, vec2, 0.F, 1.F);
|
||||
|
||||
vec1.normalizeL2();
|
||||
vec2.normalizeL2();
|
||||
|
||||
const NormalizedCosineDistance<Size, float> dist{ vec1 };
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(dist(vec2));
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * Size);
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(BM_CosineDistance, 4);
|
||||
BENCHMARK_TEMPLATE(BM_CosineDistance, 50);
|
||||
BENCHMARK_TEMPLATE(BM_CosineDistance, 160);
|
||||
BENCHMARK_TEMPLATE(BM_NormalizedCosineDistance, 4);
|
||||
BENCHMARK_TEMPLATE(BM_NormalizedCosineDistance, 50);
|
||||
BENCHMARK_TEMPLATE(BM_NormalizedCosineDistance, 160);
|
||||
BENCHMARK_TEMPLATE(BM_NormalizedCosineDistance_Functor, 4);
|
||||
BENCHMARK_TEMPLATE(BM_NormalizedCosineDistance_Functor, 50);
|
||||
BENCHMARK_TEMPLATE(BM_NormalizedCosineDistance_Functor, 160);
|
||||
} // namespace lms::math::benchs
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <random>
|
||||
|
||||
#include "core/Random.hpp"
|
||||
|
||||
#include "math/DotProduct.hpp"
|
||||
|
||||
namespace lms::math::benchs
|
||||
{
|
||||
template<std::size_t Size>
|
||||
static void BM_DotProduct(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand randomEngine{ 0 };
|
||||
|
||||
Vector<Size, float> vec1;
|
||||
Vector<Size, float> vec2;
|
||||
|
||||
core::random::fillContainer(randomEngine, vec1, 0.F, 1.F);
|
||||
core::random::fillContainer(randomEngine, vec2, 0.F, 1.F);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(computeDotProduct(vec1, vec2));
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * Size);
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(BM_DotProduct, 4);
|
||||
BENCHMARK_TEMPLATE(BM_DotProduct, 50);
|
||||
BENCHMARK_TEMPLATE(BM_DotProduct, 160);
|
||||
} // namespace lms::math::benchs
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <random>
|
||||
|
||||
#include "core/Random.hpp"
|
||||
|
||||
#include "math/EuclideanDistance.hpp"
|
||||
|
||||
namespace lms::core::benchs
|
||||
{
|
||||
template<std::size_t Size>
|
||||
static void BM_SquaredEuclideanDistance(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand randomEngine{ 0 };
|
||||
|
||||
math::Vector<Size, float> vec1;
|
||||
math::Vector<Size, float> vec2;
|
||||
|
||||
core::random::fillContainer(randomEngine, vec1, 0.F, 1.F);
|
||||
core::random::fillContainer(randomEngine, vec2, 0.F, 1.F);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(math::computeEuclideanSquaredDistance(vec1, vec2));
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * Size);
|
||||
}
|
||||
|
||||
template<std::size_t Size>
|
||||
static void BM_SquaredEuclideanDistanceWithWeights(benchmark::State& state)
|
||||
{
|
||||
std::minstd_rand randomEngine{ 0 };
|
||||
|
||||
math::Vector<Size, float> vec1;
|
||||
math::Vector<Size, float> vec2;
|
||||
math::Vector<Size, float> weights;
|
||||
|
||||
core::random::fillContainer(randomEngine, vec1, 0.F, 1.F);
|
||||
core::random::fillContainer(randomEngine, vec2, 0.F, 1.F);
|
||||
core::random::fillContainer(randomEngine, weights, 0.F, 1.F);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
benchmark::DoNotOptimize(math::computeEuclideanSquaredDistanceWithWeights(vec1, vec2, weights));
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * Size);
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(BM_SquaredEuclideanDistance, 4);
|
||||
BENCHMARK_TEMPLATE(BM_SquaredEuclideanDistance, 50);
|
||||
BENCHMARK_TEMPLATE(BM_SquaredEuclideanDistance, 160);
|
||||
BENCHMARK_TEMPLATE(BM_SquaredEuclideanDistanceWithWeights, 4);
|
||||
BENCHMARK_TEMPLATE(BM_SquaredEuclideanDistanceWithWeights, 50);
|
||||
BENCHMARK_TEMPLATE(BM_SquaredEuclideanDistanceWithWeights, 160);
|
||||
} // namespace lms::core::benchs
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <numbers>
|
||||
#include <vector>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "core/AlignedHeapArray.hpp"
|
||||
|
||||
#include "math/FFT.hpp"
|
||||
|
||||
namespace lms::math::benchs
|
||||
{
|
||||
namespace
|
||||
{
|
||||
template<typename FloatType>
|
||||
std::vector<FloatType> generateTestSignal(std::size_t n)
|
||||
{
|
||||
std::vector<FloatType> data(n);
|
||||
|
||||
for (std::size_t i{}; i < n; ++i)
|
||||
data[i] = std::sin(static_cast<FloatType>(2) * std::numbers::pi_v<FloatType> * static_cast<FloatType>(i) / static_cast<FloatType>(n));
|
||||
|
||||
return data;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template<std::size_t N, typename FloatType>
|
||||
void BM_FFT(benchmark::State& state)
|
||||
{
|
||||
const std::vector<FloatType> inputSignal{ generateTestSignal<FloatType>(N) };
|
||||
|
||||
FixedRealFFTPlan<N, FloatType> fft;
|
||||
core::AlignedHeapArray<FloatType, FixedRealFFTPlan<N>::minBufferAlignment> input{ N };
|
||||
core::AlignedHeapArray<std::complex<FloatType>, FixedRealFFTPlan<N>::minBufferAlignment> output{ fft.getOutputSize() };
|
||||
|
||||
std::copy(inputSignal.begin(), inputSignal.end(), input.begin());
|
||||
|
||||
for (auto _ : state)
|
||||
fft.apply({ input.data(), input.size() }, { output.data(), output.size() });
|
||||
|
||||
state.counters["Samples/s"] = benchmark::Counter{ static_cast<double>(N), benchmark::Counter::kIsIterationInvariantRate };
|
||||
state.counters["FFT/s"] = benchmark::Counter{ 1.0, benchmark::Counter::kIsIterationInvariantRate };
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FFT<512, float>);
|
||||
BENCHMARK(BM_FFT<1024, float>);
|
||||
BENCHMARK(BM_FFT<2048, float>);
|
||||
BENCHMARK(BM_FFT<512, double>);
|
||||
BENCHMARK(BM_FFT<1024, double>);
|
||||
BENCHMARK(BM_FFT<2048, double>);
|
||||
|
||||
} // namespace lms::math::benchs
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<typename VectorType>
|
||||
class CentroidCalculator
|
||||
{
|
||||
public:
|
||||
static_assert(!std::is_const_v<VectorType>);
|
||||
|
||||
using value_type = typename VectorType::value_type;
|
||||
using size_type = std::size_t;
|
||||
|
||||
constexpr void add(const VectorType& value)
|
||||
{
|
||||
_sum += value;
|
||||
++_count;
|
||||
}
|
||||
|
||||
template<typename InputIt>
|
||||
constexpr void add(InputIt first, InputIt last)
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
add(*first);
|
||||
}
|
||||
|
||||
constexpr VectorType finalize() const
|
||||
{
|
||||
assert(_count > 0);
|
||||
VectorType result{ _sum };
|
||||
result *= static_cast<value_type>(1) / static_cast<value_type>(_count);
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr VectorType finalizeNormalized() const
|
||||
{
|
||||
VectorType result{ finalize() };
|
||||
result.normalizeL2();
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr bool empty() const
|
||||
{
|
||||
return _count == 0;
|
||||
}
|
||||
|
||||
constexpr size_type count() const
|
||||
{
|
||||
return _count;
|
||||
}
|
||||
|
||||
private:
|
||||
VectorType _sum;
|
||||
size_type _count{};
|
||||
};
|
||||
|
||||
template<typename VectorType>
|
||||
constexpr VectorType computeCentroid(std::span<const VectorType> values)
|
||||
{
|
||||
CentroidCalculator<VectorType> calculator;
|
||||
calculator.add(std::cbegin(values), std::cend(values));
|
||||
return calculator.finalize();
|
||||
}
|
||||
|
||||
template<typename VectorType>
|
||||
constexpr VectorType computeNormalizedCentroid(std::span<const VectorType> values)
|
||||
{
|
||||
CentroidCalculator<VectorType> calculator;
|
||||
calculator.add(std::cbegin(values), std::cend(values));
|
||||
return calculator.finalizeNormalized();
|
||||
}
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <type_traits>
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
/// Computes the Chamfer distance from set A to set B.
|
||||
/// For each element in A, finds the nearest element in B and sums these minimum distances.
|
||||
/// The result is normalized by the size of A.
|
||||
///
|
||||
/// @tparam DistanceFunc A functor type: constructed with an element of A as ref,
|
||||
/// then called with each element of B as target.
|
||||
/// @param A A forward range of vectors
|
||||
/// @param B A forward range of vectors (same element type as A)
|
||||
/// @return The normalized sum of minimum distances from A to B
|
||||
template<typename DistanceFunc,
|
||||
std::ranges::forward_range RangeA,
|
||||
std::ranges::forward_range RangeB>
|
||||
requires std::same_as<std::ranges::range_value_t<RangeA>,
|
||||
std::ranges::range_value_t<RangeB>>
|
||||
auto chamferDistanceAtoB(const RangeA& A, const RangeB& B)
|
||||
{
|
||||
using Vector = std::ranges::range_value_t<RangeA>;
|
||||
using ValueType = std::invoke_result_t<DistanceFunc, const Vector&>;
|
||||
|
||||
assert(!std::ranges::empty(A));
|
||||
assert(!std::ranges::empty(B));
|
||||
|
||||
ValueType total{};
|
||||
std::size_t countA{};
|
||||
|
||||
for (const auto& a : A)
|
||||
{
|
||||
DistanceFunc distFunc{ a };
|
||||
ValueType bestDist{ std::numeric_limits<ValueType>::max() };
|
||||
|
||||
for (const auto& b : B)
|
||||
{
|
||||
const ValueType dist{ distFunc(b) };
|
||||
if (dist < bestDist)
|
||||
bestDist = dist;
|
||||
}
|
||||
|
||||
total += bestDist;
|
||||
++countA;
|
||||
}
|
||||
|
||||
return total / static_cast<ValueType>(countA);
|
||||
}
|
||||
|
||||
/// Computes the symmetrical Chamfer distance between two sets.
|
||||
/// Returns the average of chamferDistanceAtoB(A, B) and chamferDistanceAtoB(B, A).
|
||||
///
|
||||
/// @tparam DistanceFunc A functor type: constructed with the ref element,
|
||||
/// then called with each candidate element.
|
||||
/// @param A A forward range of vectors
|
||||
/// @param B A forward range of vectors (same element type as A)
|
||||
/// @return The symmetrical Chamfer distance
|
||||
template<typename DistanceFunc,
|
||||
std::ranges::forward_range RangeA,
|
||||
std::ranges::forward_range RangeB>
|
||||
requires std::same_as<std::ranges::range_value_t<RangeA>,
|
||||
std::ranges::range_value_t<RangeB>>
|
||||
auto symmetricalChamferDistance(const RangeA& A, const RangeB& B)
|
||||
{
|
||||
const auto aToB{ chamferDistanceAtoB<DistanceFunc>(A, B) };
|
||||
const auto bToA{ chamferDistanceAtoB<DistanceFunc>(B, A) };
|
||||
return (aToB + bToA) / static_cast<decltype(aToB)>(2);
|
||||
}
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<std::size_t Size, typename FloatType>
|
||||
FloatType computeCosineDistance(const Vector<Size, FloatType>& a, const Vector<Size, FloatType>& b)
|
||||
{
|
||||
constexpr FloatType smallEpsilon{ 1e-12F };
|
||||
|
||||
FloatType dot{};
|
||||
FloatType lhsNormSquared{};
|
||||
FloatType rhsNormSquared{};
|
||||
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
dot += a[i] * b[i];
|
||||
lhsNormSquared += a[i] * a[i];
|
||||
rhsNormSquared += b[i] * b[i];
|
||||
}
|
||||
|
||||
const FloatType denom{ std::sqrt(lhsNormSquared * rhsNormSquared) };
|
||||
if (denom <= smallEpsilon)
|
||||
return FloatType{ 1.F };
|
||||
|
||||
FloatType cosineSimilarity{ dot / denom };
|
||||
cosineSimilarity = std::clamp(cosineSimilarity, FloatType{ -1.F }, FloatType{ 1.F });
|
||||
|
||||
return FloatType{ 1.F } - cosineSimilarity;
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType = float>
|
||||
struct CosineDistance
|
||||
{
|
||||
constexpr CosineDistance(const Vector<Size, FloatType>& ref)
|
||||
: _ref{ ref }
|
||||
{
|
||||
}
|
||||
|
||||
FloatType operator()(const Vector<Size, FloatType>& a) const
|
||||
{
|
||||
return computeCosineDistance(_ref, a);
|
||||
}
|
||||
|
||||
const Vector<Size, FloatType>& _ref;
|
||||
};
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "math/SquareMatrix.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<std::size_t Size, typename FloatType = float>
|
||||
class CovarianceMatrixCalculator
|
||||
{
|
||||
public:
|
||||
static_assert(!std::is_const_v<FloatType>);
|
||||
|
||||
using CovarianceMatrix = SquareMatrix<FloatType, Size>;
|
||||
|
||||
constexpr void add(const Vector<Size, FloatType>& centeredVector)
|
||||
{
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j <= i; ++j)
|
||||
_cov[i][j] += centeredVector[i] * centeredVector[j];
|
||||
}
|
||||
|
||||
++_count;
|
||||
}
|
||||
|
||||
template<typename InputIt>
|
||||
constexpr void add(InputIt first, InputIt last)
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
add(*first);
|
||||
}
|
||||
|
||||
constexpr void finalizeSample(CovarianceMatrix& out) const
|
||||
{
|
||||
out.fill(FloatType{});
|
||||
if (_count < 2)
|
||||
return;
|
||||
|
||||
const FloatType divisor{ static_cast<FloatType>(_count - 1) };
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j <= i; ++j)
|
||||
{
|
||||
const FloatType value{ _cov[i][j] / divisor };
|
||||
out[i][j] = value;
|
||||
out[j][i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void finalizePopulation(CovarianceMatrix& out) const
|
||||
{
|
||||
out.fill(FloatType{});
|
||||
if (_count == 0)
|
||||
return;
|
||||
|
||||
const FloatType divisor{ static_cast<FloatType>(_count) };
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j <= i; ++j)
|
||||
{
|
||||
const FloatType value{ _cov[i][j] / divisor };
|
||||
out[i][j] = value;
|
||||
out[j][i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool empty() const
|
||||
{
|
||||
return _count == 0;
|
||||
}
|
||||
|
||||
constexpr std::size_t count() const
|
||||
{
|
||||
return _count;
|
||||
}
|
||||
|
||||
private:
|
||||
CovarianceMatrix _cov{};
|
||||
std::size_t _count{};
|
||||
};
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<std::size_t Size, typename FloatType>
|
||||
constexpr FloatType computeDotProduct(const Vector<Size, FloatType>& a, const Vector<Size, FloatType>& b)
|
||||
{
|
||||
FloatType res{};
|
||||
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
res += a[i] * b[i];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType = float>
|
||||
struct DotProduct
|
||||
{
|
||||
constexpr DotProduct(const Vector<Size, FloatType>& ref)
|
||||
: _ref{ ref }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr FloatType operator()(const Vector<Size, FloatType>& a) const
|
||||
{
|
||||
return computeDotProduct(_ref, a);
|
||||
}
|
||||
|
||||
const Vector<Size, FloatType>& _ref;
|
||||
};
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <span>
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<typename FloatType = float>
|
||||
FloatType entropy(std::span<const FloatType> c)
|
||||
{
|
||||
FloatType sum{};
|
||||
|
||||
for (auto v : c)
|
||||
sum += v;
|
||||
|
||||
constexpr FloatType epsilon{ 1e-12 };
|
||||
|
||||
if (sum <= epsilon)
|
||||
return {};
|
||||
|
||||
const FloatType invSum{ FloatType{ 1 } / sum };
|
||||
|
||||
FloatType res{};
|
||||
for (auto v : c)
|
||||
{
|
||||
if (v <= epsilon)
|
||||
continue;
|
||||
|
||||
const FloatType p{ v * invSum };
|
||||
res -= p * std::log(p);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<std::size_t Size, typename FloatType>
|
||||
constexpr FloatType computeEuclideanSquaredDistance(const Vector<Size, FloatType>& a, const Vector<Size, FloatType>& b)
|
||||
{
|
||||
FloatType res{};
|
||||
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
const FloatType diff{ a[i] - b[i] };
|
||||
res += diff * diff;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType>
|
||||
constexpr FloatType computeEuclideanSquaredDistanceWithWeights(const Vector<Size, FloatType>& a, const Vector<Size, FloatType>& b, const Vector<Size, FloatType>& weights)
|
||||
{
|
||||
FloatType res{};
|
||||
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
const FloatType diff{ a[i] - b[i] };
|
||||
res += diff * diff * weights[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType = float>
|
||||
struct SquaredEuclideanDistance
|
||||
{
|
||||
constexpr SquaredEuclideanDistance(const Vector<Size, FloatType>& ref)
|
||||
: _ref{ ref } {}
|
||||
|
||||
constexpr FloatType operator()(const Vector<Size, FloatType>& a) const
|
||||
{
|
||||
return computeEuclideanSquaredDistance(_ref, a);
|
||||
}
|
||||
|
||||
const Vector<Size, FloatType>& _ref;
|
||||
};
|
||||
|
||||
template<std::size_t Size, typename FloatType = float>
|
||||
struct SquaredEuclideanDistanceWithWeights
|
||||
{
|
||||
constexpr SquaredEuclideanDistanceWithWeights(const Vector<Size, FloatType>& ref, const Vector<Size, FloatType>& weights)
|
||||
: _ref{ ref }
|
||||
, _weights{ weights }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr FloatType operator()(const Vector<Size, FloatType>& a) const
|
||||
{
|
||||
return computeEuclideanSquaredDistanceWithWeights(_ref, a, _weights);
|
||||
}
|
||||
|
||||
const Vector<Size, FloatType>& _ref;
|
||||
const Vector<Size, FloatType>& _weights;
|
||||
};
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <cstddef>
|
||||
#include <numbers>
|
||||
#include <span>
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<std::size_t Size, typename FloatType = float>
|
||||
class FixedRealFFTPlan
|
||||
{
|
||||
static_assert(std::has_single_bit(Size), "Size must be power of two");
|
||||
|
||||
public:
|
||||
static constexpr std::size_t minBufferAlignment{ 32 };
|
||||
|
||||
constexpr FixedRealFFTPlan()
|
||||
{
|
||||
// Twiddles
|
||||
for (std::size_t k{}; k < halfSize; ++k)
|
||||
{
|
||||
FloatType angle{ FloatType(-2) * std::numbers::pi_v<FloatType> * k / Size };
|
||||
_twiddles[k] = std::complex<FloatType>{ std::cos(angle), std::sin(angle) };
|
||||
}
|
||||
|
||||
// Bit-reversal for halfSize FFT
|
||||
constexpr std::size_t logHalf{ std::countr_zero(halfSize) };
|
||||
for (std::size_t i{}; i < halfSize; ++i)
|
||||
_bitrev[i] = reverseBits(i, logHalf);
|
||||
}
|
||||
|
||||
constexpr static std::size_t getInputSize() noexcept { return Size; }
|
||||
constexpr static std::size_t getOutputSize() noexcept { return halfSize + 1; }
|
||||
|
||||
constexpr void apply(std::span<const FloatType> input, std::span<std::complex<FloatType>> output) const noexcept
|
||||
{
|
||||
assert(input.size() == Size);
|
||||
assert(output.size() == halfSize + 1);
|
||||
|
||||
assert(reinterpret_cast<std::uintptr_t>(input.data()) % minBufferAlignment == 0);
|
||||
assert(reinterpret_cast<std::uintptr_t>(output.data()) % minBufferAlignment == 0);
|
||||
|
||||
// Pack real -> complex
|
||||
alignas(minBufferAlignment) std::array<std::complex<FloatType>, halfSize> data;
|
||||
for (std::size_t i{}; i < halfSize; ++i)
|
||||
data[i] = std::complex<FloatType>{ input[2 * i], input[2 * i + 1] };
|
||||
|
||||
fft(data);
|
||||
|
||||
// Real FFT post-process
|
||||
output[0] = std::complex<FloatType>{ data[0].real() + data[0].imag(), FloatType{} };
|
||||
output[halfSize] = std::complex<FloatType>{ data[0].real() - data[0].imag(), FloatType{} };
|
||||
for (std::size_t k{ 1 }; k <= halfSize / 2; ++k)
|
||||
{
|
||||
const auto a{ data[k] };
|
||||
const auto b{ std::conj(data[(halfSize - k) & (halfSize - 1)]) };
|
||||
|
||||
const auto even{ (a + b) * std::complex<FloatType>{ FloatType(0.5), FloatType{} } };
|
||||
const auto odd{ (a - b) * std::complex<FloatType>{ FloatType{}, FloatType(-0.5) } };
|
||||
|
||||
const auto& W{ _twiddles[k] };
|
||||
const auto t{ W * odd };
|
||||
|
||||
output[k] = even + t;
|
||||
output[halfSize - k] = std::conj(even - t);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t halfSize{ Size / 2 };
|
||||
|
||||
alignas(minBufferAlignment) std::array<std::complex<FloatType>, halfSize> _twiddles{};
|
||||
std::array<std::size_t, halfSize> _bitrev{};
|
||||
|
||||
static constexpr std::size_t reverseBits(std::size_t x, std::size_t bitCount) noexcept
|
||||
{
|
||||
std::size_t y{};
|
||||
for (std::size_t i{}; i < bitCount; ++i)
|
||||
{
|
||||
y = (y << 1) | (x & 1);
|
||||
x >>= 1;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
constexpr void fft(std::array<std::complex<FloatType>, halfSize>& data) const noexcept
|
||||
{
|
||||
// Bit reversal
|
||||
for (std::size_t i{}; i < halfSize; ++i)
|
||||
{
|
||||
const auto j{ _bitrev[i] };
|
||||
if (i < j)
|
||||
std::swap(data[i], data[j]);
|
||||
}
|
||||
|
||||
fftStages<1>(data);
|
||||
}
|
||||
|
||||
template<std::size_t Stage>
|
||||
constexpr void fftStages(std::array<std::complex<FloatType>, halfSize>& data) const noexcept
|
||||
{
|
||||
constexpr std::size_t len{ 1U << Stage };
|
||||
|
||||
if constexpr (len <= halfSize)
|
||||
{
|
||||
constexpr std::size_t half{ len >> 1 };
|
||||
constexpr std::size_t step{ Size / len };
|
||||
|
||||
for (std::size_t i{}; i < halfSize; i += len)
|
||||
{
|
||||
for (std::size_t j{}; j < half; ++j)
|
||||
{
|
||||
auto& u{ data[i + j] };
|
||||
auto& v{ data[i + j + half] };
|
||||
|
||||
const auto t{ _twiddles[j * step] * v };
|
||||
|
||||
v = u - t;
|
||||
u = u + t;
|
||||
}
|
||||
}
|
||||
|
||||
fftStages<Stage + 1>(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "math/EuclideanDistance.hpp"
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<typename VectorType>
|
||||
class MedoidCalculator
|
||||
{
|
||||
public:
|
||||
static_assert(!std::is_const_v<VectorType>);
|
||||
|
||||
using value_type = typename VectorType::value_type;
|
||||
using size_type = std::size_t;
|
||||
|
||||
// Add a single vector, returning its index
|
||||
size_type add(const VectorType& value)
|
||||
{
|
||||
_vectors.push_back(value);
|
||||
return _vectors.size() - 1;
|
||||
}
|
||||
|
||||
// Compute the medoid: the vector with minimum sum of squared distances to all others
|
||||
// Returns the index of the medoid in the added vectors
|
||||
size_type findMedoidIndex() const
|
||||
{
|
||||
assert(!empty());
|
||||
|
||||
size_type medoidIndex{};
|
||||
value_type minTotalDistance{ std::numeric_limits<value_type>::max() };
|
||||
|
||||
for (size_type i{}; i < _vectors.size(); ++i)
|
||||
{
|
||||
value_type totalDistance{};
|
||||
const SquaredEuclideanDistance distFunc{ _vectors[i] };
|
||||
for (size_type j{}; j < _vectors.size(); ++j)
|
||||
{
|
||||
if (i != j)
|
||||
totalDistance += distFunc(_vectors[j]);
|
||||
}
|
||||
|
||||
if (totalDistance < minTotalDistance)
|
||||
{
|
||||
minTotalDistance = totalDistance;
|
||||
medoidIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return medoidIndex;
|
||||
}
|
||||
|
||||
// Compute the medoid vector itself
|
||||
VectorType finalize() const
|
||||
{
|
||||
return _vectors[findMedoidIndex()];
|
||||
}
|
||||
|
||||
// Get a specific vector by index
|
||||
const VectorType& getVector(size_type index) const
|
||||
{
|
||||
assert(index < _vectors.size());
|
||||
return _vectors[index];
|
||||
}
|
||||
|
||||
// Query methods
|
||||
bool empty() const
|
||||
{
|
||||
return _vectors.empty();
|
||||
}
|
||||
|
||||
size_type count() const
|
||||
{
|
||||
return _vectors.size();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
_vectors.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<VectorType> _vectors;
|
||||
};
|
||||
|
||||
template<typename VectorType>
|
||||
VectorType computeMedoid(std::span<const VectorType> values)
|
||||
{
|
||||
MedoidCalculator<VectorType> calculator;
|
||||
for (const auto& value : values)
|
||||
calculator.add(value);
|
||||
return calculator.finalize();
|
||||
}
|
||||
|
||||
template<typename VectorType>
|
||||
VectorType computeNormalizedMedoid(std::span<const VectorType> values)
|
||||
{
|
||||
MedoidCalculator<VectorType> calculator;
|
||||
for (const auto& value : values)
|
||||
calculator.add(value);
|
||||
return calculator.finalizeNormalized();
|
||||
}
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "math/DotProduct.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
// Returns the cosine distance in [0, 1] for L2-normalized vectors:
|
||||
// 0 = identical direction, 0.5 = orthogonal, 1 = opposite directions.
|
||||
template<std::size_t Size, typename FloatType>
|
||||
constexpr FloatType computeNormalizedCosineDistance(
|
||||
const Vector<Size, FloatType>& a,
|
||||
const Vector<Size, FloatType>& b)
|
||||
{
|
||||
return (FloatType{ 1 } - computeDotProduct(a, b)) / FloatType{ 2 };
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType = float>
|
||||
struct NormalizedCosineDistance
|
||||
{
|
||||
constexpr NormalizedCosineDistance(const Vector<Size, FloatType>& ref)
|
||||
: _ref{ ref }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr FloatType operator()(const Vector<Size, FloatType>& a) const
|
||||
{
|
||||
return (FloatType{ 1 } - computeDotProduct(_ref, a)) / FloatType{ 2 };
|
||||
}
|
||||
|
||||
const Vector<Size, FloatType>& _ref;
|
||||
};
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
||||
#include "math/SquareMatrix.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<std::size_t Size, typename FloatType>
|
||||
FloatType dotProduct(const Vector<Size, FloatType>& a, const Vector<Size, FloatType>& b)
|
||||
{
|
||||
FloatType result{};
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
result += a[i] * b[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename FloatType, std::size_t Size>
|
||||
void computeEigenpairsViaPowerIteration(const SquareMatrix<FloatType, Size>& covariance,
|
||||
std::array<Vector<Size, FloatType>, Size>& eigenvectors,
|
||||
Vector<Size, FloatType>& eigenvalues,
|
||||
std::size_t maxIterations = 200,
|
||||
FloatType epsilon = static_cast<FloatType>(1e-15))
|
||||
{
|
||||
// Power iteration with Deflation for computing eigendecomposition.
|
||||
// Iteratively finds the largest eigenvalue and corresponding eigenvector,
|
||||
// then removes it from the matrix and repeats.
|
||||
|
||||
auto covarianceCopy{ std::make_unique<SquareMatrix<FloatType, Size>>(covariance) };
|
||||
std::minstd_rand rng{ 42 };
|
||||
std::uniform_real_distribution<FloatType> dist{ static_cast<FloatType>(-1.0), static_cast<FloatType>(1.0) };
|
||||
|
||||
for (std::size_t k{}; k < Size; ++k)
|
||||
{
|
||||
Vector<Size, FloatType> v;
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
v[i] = dist(rng);
|
||||
|
||||
FloatType prevEigenvalue{};
|
||||
for (std::size_t iter{}; iter < maxIterations; ++iter)
|
||||
{
|
||||
Vector<Size, FloatType> Av;
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j < Size; ++j)
|
||||
Av[i] += (*covarianceCopy)[i][j] * v[j];
|
||||
}
|
||||
|
||||
FloatType normSquared{};
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
normSquared += Av[i] * Av[i];
|
||||
|
||||
if (normSquared < epsilon)
|
||||
break;
|
||||
|
||||
const FloatType norm{ std::sqrt(normSquared) };
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
v[i] = Av[i] / norm;
|
||||
|
||||
eigenvalues[k] = norm;
|
||||
|
||||
// Early exit if eigenvalue converged
|
||||
if (iter > 0 && std::abs(norm - prevEigenvalue) < epsilon)
|
||||
break;
|
||||
|
||||
prevEigenvalue = norm;
|
||||
}
|
||||
|
||||
eigenvectors[k] = v;
|
||||
|
||||
// Deflate matrix: A = A - lambda * v * v^T
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j < Size; ++j)
|
||||
(*covarianceCopy)[i][j] -= eigenvalues[k] * v[i] * v[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t BasisCount, std::size_t FeatureCount, typename FloatType>
|
||||
void projectOntoBasis(const std::array<std::array<FloatType, FeatureCount>, BasisCount>& basis,
|
||||
const Vector<FeatureCount, FloatType>& centered,
|
||||
Vector<BasisCount, FloatType>& output,
|
||||
const std::array<FloatType, BasisCount>& scales)
|
||||
{
|
||||
for (std::size_t k{}; k < BasisCount; ++k)
|
||||
{
|
||||
FloatType sum{};
|
||||
for (std::size_t j{}; j < FeatureCount; ++j)
|
||||
sum += basis[k][j] * centered[j];
|
||||
output[k] = sum * scales[k];
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType>
|
||||
FloatType pearsonCorrelation(const Vector<Size, FloatType>& a, const Vector<Size, FloatType>& b)
|
||||
{
|
||||
static_assert(Size > 0);
|
||||
|
||||
FloatType meanA{};
|
||||
FloatType meanB{};
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
meanA += a[i];
|
||||
meanB += b[i];
|
||||
}
|
||||
|
||||
meanA /= static_cast<FloatType>(Size);
|
||||
meanB /= static_cast<FloatType>(Size);
|
||||
|
||||
FloatType cov{};
|
||||
FloatType varA{};
|
||||
FloatType varB{};
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
const FloatType da{ a[i] - meanA };
|
||||
const FloatType db{ b[i] - meanB };
|
||||
cov += da * db;
|
||||
varA += da * da;
|
||||
varB += db * db;
|
||||
}
|
||||
|
||||
if (varA <= static_cast<FloatType>(0) || varB <= static_cast<FloatType>(0))
|
||||
return static_cast<FloatType>(0);
|
||||
|
||||
return cov / std::sqrt(varA * varB);
|
||||
}
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<typename T, std::size_t N>
|
||||
class SquareMatrix
|
||||
{
|
||||
public:
|
||||
static_assert(N > 0, "SquareMatrix size must be positive");
|
||||
|
||||
using Row = std::array<T, N>;
|
||||
|
||||
constexpr SquareMatrix() = default;
|
||||
|
||||
constexpr explicit SquareMatrix(const T& value)
|
||||
{
|
||||
fill(value);
|
||||
}
|
||||
|
||||
constexpr void fill(const T& value)
|
||||
{
|
||||
for (auto& row : _values)
|
||||
row.fill(value);
|
||||
}
|
||||
|
||||
constexpr std::size_t size() const noexcept
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
||||
constexpr Row& operator[](std::size_t index)
|
||||
{
|
||||
assert(index < N);
|
||||
return _values[index];
|
||||
}
|
||||
|
||||
constexpr const Row& operator[](std::size_t index) const
|
||||
{
|
||||
assert(index < N);
|
||||
return _values[index];
|
||||
}
|
||||
|
||||
constexpr auto begin()
|
||||
{
|
||||
return _values.begin();
|
||||
}
|
||||
|
||||
constexpr auto end()
|
||||
{
|
||||
return _values.end();
|
||||
}
|
||||
|
||||
constexpr auto begin() const
|
||||
{
|
||||
return _values.begin();
|
||||
}
|
||||
|
||||
constexpr auto end() const
|
||||
{
|
||||
return _values.end();
|
||||
}
|
||||
|
||||
constexpr auto cbegin() const
|
||||
{
|
||||
return _values.cbegin();
|
||||
}
|
||||
|
||||
constexpr auto cend() const
|
||||
{
|
||||
return _values.cend();
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<Row, N> _values{};
|
||||
};
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
bool choleskyDecompose(const SquareMatrix<T, N>& A, SquareMatrix<T, N>& L)
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>, "Cholesky decomposition requires floating point type");
|
||||
|
||||
L.fill(T{});
|
||||
|
||||
for (std::size_t i{}; i < N; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j <= i; ++j)
|
||||
{
|
||||
T sum{};
|
||||
|
||||
for (std::size_t k{}; k < j; ++k)
|
||||
sum += L[i][k] * L[j][k];
|
||||
|
||||
if (i == j)
|
||||
{
|
||||
const T val{ A[i][i] - sum };
|
||||
if (val <= T{})
|
||||
return false;
|
||||
|
||||
L[i][j] = std::sqrt(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
L[i][j] = (A[i][j] - sum) / L[j][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
void invertLowerTriangular(const SquareMatrix<T, N>& L, SquareMatrix<T, N>& Linv)
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>, "Requires floating point type");
|
||||
|
||||
Linv.fill(T{});
|
||||
|
||||
for (std::size_t i{}; i < N; ++i)
|
||||
{
|
||||
assert(std::abs(L[i][i]) > std::numeric_limits<T>::epsilon());
|
||||
Linv[i][i] = T{ 1 } / L[i][i];
|
||||
|
||||
for (std::size_t j{}; j < i; ++j)
|
||||
{
|
||||
T sum{};
|
||||
|
||||
for (std::size_t k{ j }; k < i; ++k)
|
||||
sum += L[i][k] * Linv[k][j];
|
||||
|
||||
Linv[i][j] = -sum / L[i][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
T computeSymmetryMaxDiff(const SquareMatrix<T, N>& M)
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>, "Requires floating point type");
|
||||
|
||||
T maxDiff{};
|
||||
|
||||
for (std::size_t i{}; i < N; ++i)
|
||||
{
|
||||
for (std::size_t j{ i + 1 }; j < N; ++j)
|
||||
{
|
||||
const T diff{ std::abs(M[i][j] - M[j][i]) };
|
||||
maxDiff = std::max(maxDiff, diff);
|
||||
}
|
||||
}
|
||||
|
||||
return maxDiff;
|
||||
}
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<typename FloatType = float>
|
||||
class StatsAccumulator
|
||||
{
|
||||
public:
|
||||
constexpr void add(FloatType x);
|
||||
constexpr std::size_t getCount() const;
|
||||
constexpr FloatType getMean() const;
|
||||
|
||||
constexpr FloatType getSampleStdDev() const;
|
||||
constexpr FloatType getSampleVariance() const;
|
||||
|
||||
constexpr FloatType getPopulationVariance() const;
|
||||
constexpr FloatType getPopulationStdDev() const;
|
||||
|
||||
private:
|
||||
std::size_t n{};
|
||||
double mean{};
|
||||
double M2{};
|
||||
};
|
||||
|
||||
template<typename FloatType>
|
||||
inline constexpr void StatsAccumulator<FloatType>::add(FloatType x)
|
||||
{
|
||||
const double n1{ static_cast<double>(n++) };
|
||||
const double nn{ static_cast<double>(n) };
|
||||
|
||||
const double delta{ static_cast<double>(x) - mean };
|
||||
const double delta_n{ delta / nn };
|
||||
const double term1{ delta * delta_n * n1 };
|
||||
|
||||
mean += delta_n;
|
||||
M2 += term1;
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
inline constexpr std::size_t StatsAccumulator<FloatType>::getCount() const
|
||||
{
|
||||
return n;
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
inline constexpr FloatType StatsAccumulator<FloatType>::getMean() const
|
||||
{
|
||||
return static_cast<FloatType>(mean);
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
inline constexpr FloatType StatsAccumulator<FloatType>::getSampleVariance() const
|
||||
{
|
||||
if (n < 2)
|
||||
return FloatType{};
|
||||
|
||||
return static_cast<FloatType>(M2 / (n - 1));
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
inline constexpr FloatType StatsAccumulator<FloatType>::getPopulationVariance() const
|
||||
{
|
||||
if (n < 1)
|
||||
return FloatType{};
|
||||
|
||||
return static_cast<FloatType>(M2 / n);
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
inline constexpr FloatType StatsAccumulator<FloatType>::getSampleStdDev() const
|
||||
{
|
||||
return static_cast<FloatType>(std::sqrt(getSampleVariance()));
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
inline constexpr FloatType StatsAccumulator<FloatType>::getPopulationStdDev() const
|
||||
{
|
||||
return static_cast<FloatType>(std::sqrt(getPopulationVariance()));
|
||||
}
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<std::size_t Size, typename FloatType = float>
|
||||
class Vector
|
||||
{
|
||||
public:
|
||||
static_assert(std::is_floating_point_v<FloatType>);
|
||||
|
||||
using value_type = FloatType;
|
||||
using Norm = FloatType;
|
||||
using Distance = FloatType;
|
||||
|
||||
constexpr explicit Vector(value_type initValue = value_type{})
|
||||
{
|
||||
_values.fill(initValue);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
requires(sizeof...(Args) == Size) && (std::convertible_to<Args, value_type> && ...)
|
||||
constexpr Vector(Args... args)
|
||||
: _values{ static_cast<value_type>(args)... }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr static std::size_t getSize() { return Size; }
|
||||
|
||||
constexpr value_type* data() { return _values.data(); }
|
||||
constexpr const value_type* data() const { return _values.data(); }
|
||||
|
||||
constexpr value_type& operator[](std::size_t index) { return _values[index]; }
|
||||
constexpr value_type operator[](std::size_t index) const { return _values[index]; }
|
||||
|
||||
constexpr Vector& operator+=(const Vector& other)
|
||||
{
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
_values[i] += other[i];
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Vector& operator-=(const Vector& other)
|
||||
{
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
_values[i] -= other[i];
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Vector& operator*=(value_type factor)
|
||||
{
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
_values[i] *= factor;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Norm computeNorm() const
|
||||
{
|
||||
Norm res{};
|
||||
for (value_type val : _values)
|
||||
res += val * val;
|
||||
return std::sqrt(res);
|
||||
}
|
||||
|
||||
void normalizeL2()
|
||||
{
|
||||
constexpr value_type smallEpsilon{ 1e-12 };
|
||||
|
||||
const Norm n{ computeNorm() };
|
||||
if (n > smallEpsilon)
|
||||
{
|
||||
for (value_type& v : _values)
|
||||
v /= n;
|
||||
}
|
||||
}
|
||||
|
||||
auto begin() { return std::begin(_values); }
|
||||
auto begin() const { return std::begin(_values); }
|
||||
auto cbegin() const { return std::cbegin(_values); }
|
||||
auto end() { return std::end(_values); }
|
||||
auto end() const { return std::end(_values); }
|
||||
auto cend() const { return std::cend(_values); }
|
||||
|
||||
private:
|
||||
std::array<value_type, Size> _values;
|
||||
};
|
||||
|
||||
template<std::size_t Size, typename FloatType>
|
||||
constexpr Vector<Size, FloatType> operator+(const Vector<Size, FloatType>& a, const Vector<Size, FloatType>& b)
|
||||
{
|
||||
Vector<Size, FloatType> res{ a };
|
||||
res += b;
|
||||
return res;
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType>
|
||||
constexpr Vector<Size, FloatType> operator-(const Vector<Size, FloatType>& a, const Vector<Size, FloatType>& b)
|
||||
{
|
||||
Vector<Size, FloatType> res{ a };
|
||||
res -= b;
|
||||
return res;
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType>
|
||||
constexpr Vector<Size, FloatType> operator*(const Vector<Size, FloatType>& v, typename Vector<Size, FloatType>::value_type scalar)
|
||||
{
|
||||
Vector<Size, FloatType> res{ v };
|
||||
res *= scalar;
|
||||
return res;
|
||||
}
|
||||
|
||||
template<std::size_t Size, typename FloatType>
|
||||
constexpr Vector<Size, FloatType> operator*(typename Vector<Size, FloatType>::value_type scalar, const Vector<Size, FloatType>& v)
|
||||
{
|
||||
return v * scalar;
|
||||
}
|
||||
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <numbers>
|
||||
#include <span>
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
template<std::size_t FrameSize, typename FloatType = float>
|
||||
class HannWindow
|
||||
{
|
||||
static_assert(FrameSize > 0, "FrameSize must be greater than zero");
|
||||
|
||||
public:
|
||||
HannWindow()
|
||||
{
|
||||
if constexpr (FrameSize == 1)
|
||||
{
|
||||
_coefficients[0] = FloatType{ 1 };
|
||||
_energy = FloatType{ 1 };
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr auto frameSize{ static_cast<FloatType>(FrameSize) };
|
||||
for (std::size_t i{}; i < FrameSize; ++i)
|
||||
{
|
||||
const auto coefficient{ static_cast<FloatType>(0.5) * (FloatType{ 1 } - std::cos(static_cast<FloatType>(2) * std::numbers::pi_v<FloatType> * static_cast<FloatType>(i) / (frameSize - FloatType{ 1 }))) };
|
||||
_coefficients[i] = coefficient;
|
||||
_energy += coefficient * coefficient;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<const FloatType, FrameSize> values() const noexcept { return _coefficients; }
|
||||
|
||||
[[nodiscard]] FloatType energy() const noexcept { return _energy; }
|
||||
|
||||
void apply(std::span<const FloatType, FrameSize> input, std::span<FloatType, FrameSize> output) const noexcept
|
||||
{
|
||||
assert(input.size() == FrameSize);
|
||||
assert(output.size() == FrameSize);
|
||||
|
||||
for (std::size_t i{}; i < FrameSize; ++i)
|
||||
output[i] = input[i] * _coefficients[i];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<FloatType, FrameSize> _coefficients{};
|
||||
FloatType _energy{};
|
||||
};
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,39 @@
|
||||
include(GoogleTest)
|
||||
|
||||
add_executable(test-math
|
||||
ChamferDistance.cpp
|
||||
CentroidCalculator.cpp
|
||||
CosineDistance.cpp
|
||||
CovarianceCalculator.cpp
|
||||
DotProduct.cpp
|
||||
Entropy.cpp
|
||||
EuclideanDistance.cpp
|
||||
FFT.cpp
|
||||
MedoidCalculator.cpp
|
||||
NormalizedCosineDistance.cpp
|
||||
PrincipalComponents.cpp
|
||||
SquareMatrix.cpp
|
||||
StatsAccumulator.cpp
|
||||
Vector.cpp
|
||||
Window.cpp
|
||||
)
|
||||
|
||||
target_include_directories(test-math PRIVATE
|
||||
../include
|
||||
)
|
||||
|
||||
target_link_libraries(test-math PRIVATE
|
||||
lmscore
|
||||
lmsmath
|
||||
Threads::Threads
|
||||
GTest::GTest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_compile_options(test-math PRIVATE
|
||||
$<$<NOT:$<CONFIG:Debug>>:-ffast-math>
|
||||
)
|
||||
|
||||
if (NOT CMAKE_CROSSCOMPILING)
|
||||
gtest_discover_tests(test-math)
|
||||
endif()
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/CentroidCalculator.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math::centroidCalculatorTests
|
||||
{
|
||||
TEST(CentroidCalculator, initialState)
|
||||
{
|
||||
CentroidCalculator<Vector<3, float>> calculator;
|
||||
|
||||
EXPECT_TRUE(calculator.empty());
|
||||
EXPECT_EQ(calculator.count(), 0U);
|
||||
}
|
||||
|
||||
TEST(CentroidCalculator, addAndFinalize)
|
||||
{
|
||||
CentroidCalculator<Vector<3, float>> calculator;
|
||||
calculator.add(Vector<3, float>{ 1.0F, 2.0F, 3.0F });
|
||||
calculator.add(Vector<3, float>{ 4.0F, 5.0F, 6.0F });
|
||||
|
||||
const Vector<3, float> result = calculator.finalize();
|
||||
|
||||
EXPECT_FLOAT_EQ(result[0], 2.5F);
|
||||
EXPECT_FLOAT_EQ(result[1], 3.5F);
|
||||
EXPECT_FLOAT_EQ(result[2], 4.5F);
|
||||
}
|
||||
|
||||
TEST(CentroidCalculator, finalizeNormalized)
|
||||
{
|
||||
CentroidCalculator<Vector<2, float>> calculator;
|
||||
calculator.add(Vector<2, float>{ 3.0F, 4.0F });
|
||||
|
||||
const Vector<2, float> result = calculator.finalizeNormalized();
|
||||
|
||||
EXPECT_NEAR(result.computeNorm(), 1.0F, 1e-6F);
|
||||
EXPECT_NEAR(result[0], 0.6F, 1e-6F);
|
||||
EXPECT_NEAR(result[1], 0.8F, 1e-6F);
|
||||
}
|
||||
|
||||
TEST(CentroidCalculator, computeCentroidSpan)
|
||||
{
|
||||
const std::array<Vector<2, float>, 2> values{
|
||||
Vector<2, float>{ 0.0F, 2.0F },
|
||||
Vector<2, float>{ 2.0F, 0.0F }
|
||||
};
|
||||
|
||||
const Vector<2, float> result = computeCentroid(std::span<const Vector<2, float>>(values));
|
||||
|
||||
EXPECT_FLOAT_EQ(result[0], 1.0F);
|
||||
EXPECT_FLOAT_EQ(result[1], 1.0F);
|
||||
}
|
||||
} // namespace lms::math::centroidCalculatorTests
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/ChamferDistance.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math::chamferDistanceTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-4F };
|
||||
|
||||
template<std::size_t Size>
|
||||
struct SimpleDistance
|
||||
{
|
||||
SimpleDistance(const Vector<Size, float>& ref)
|
||||
: _ref{ ref } {}
|
||||
|
||||
float operator()(const Vector<Size, float>& b) const
|
||||
{
|
||||
float sum{};
|
||||
for (std::size_t i{}; i < Size; ++i)
|
||||
{
|
||||
const float diff{ _ref[i] - b[i] };
|
||||
sum += diff * diff;
|
||||
}
|
||||
return std::sqrt(sum);
|
||||
}
|
||||
|
||||
const Vector<Size, float>& _ref;
|
||||
};
|
||||
|
||||
TEST(ChamferDistance, singleElementSets)
|
||||
{
|
||||
const Vector<2, float> A[]{ { 0.F, 0.F } };
|
||||
const Vector<2, float> B[]{ { 3.F, 4.F } };
|
||||
|
||||
const float result{ chamferDistanceAtoB<SimpleDistance<2>>(A, B) };
|
||||
|
||||
const float expected{ 5.F }; // sqrt(3^2 + 4^2) = 5
|
||||
EXPECT_NEAR(result, expected, epsilon);
|
||||
}
|
||||
|
||||
TEST(ChamferDistance, identicalSets)
|
||||
{
|
||||
const Vector<2, float> A[]{ { 1.F, 2.F }, { 3.F, 4.F } };
|
||||
|
||||
const float result{ chamferDistanceAtoB<SimpleDistance<2>>(A, A) };
|
||||
|
||||
EXPECT_NEAR(result, 0.F, epsilon);
|
||||
}
|
||||
|
||||
TEST(ChamferDistance, asymmetricDistance)
|
||||
{
|
||||
// A = {(0,0), (1,0)}, B = {(0,0), (2,0)}
|
||||
// For a=(0,0): min(dist to (0,0), dist to (2,0)) = 0
|
||||
// For a=(1,0): min(dist to (0,0), dist to (2,0)) = min(1, 1) = 1
|
||||
// Average = (0 + 1) / 2 = 0.5
|
||||
|
||||
const Vector<2, float> A[]{ { 0.F, 0.F }, { 1.F, 0.F } };
|
||||
const Vector<2, float> B[]{ { 0.F, 0.F }, { 2.F, 0.F } };
|
||||
|
||||
const float result{ chamferDistanceAtoB<SimpleDistance<2>>(A, B) };
|
||||
|
||||
EXPECT_NEAR(result, 0.5F, epsilon);
|
||||
}
|
||||
|
||||
TEST(ChamferDistance, symmetricalDistance)
|
||||
{
|
||||
const Vector<2, float> A[]{ { 0.F, 0.F }, { 2.F, 0.F } };
|
||||
const Vector<2, float> B[]{ { 0.F, 0.F }, { 1.F, 0.F } };
|
||||
|
||||
const float symDist{ symmetricalChamferDistance<SimpleDistance<2>>(A, B) };
|
||||
|
||||
const float aToB{ chamferDistanceAtoB<SimpleDistance<2>>(A, B) };
|
||||
const float bToA{ chamferDistanceAtoB<SimpleDistance<2>>(B, A) };
|
||||
const float expected{ (aToB + bToA) / 2.F };
|
||||
|
||||
EXPECT_NEAR(symDist, expected, epsilon);
|
||||
}
|
||||
|
||||
TEST(ChamferDistance, largerSets)
|
||||
{
|
||||
// A has 3 elements, B has 2 elements
|
||||
const Vector<2, float> A[]{ { 0.F, 0.F }, { 1.F, 1.F }, { 2.F, 2.F } };
|
||||
const Vector<2, float> B[]{ { 0.F, 0.F }, { 3.F, 3.F } };
|
||||
|
||||
const float result{ chamferDistanceAtoB<SimpleDistance<2>>(A, B) };
|
||||
|
||||
// a1: min(0, sqrt(27)) = 0
|
||||
// a2: min(sqrt(2), sqrt(8)) = sqrt(2)
|
||||
// a3: min(sqrt(8), sqrt(2)) = sqrt(2)
|
||||
// Average = (0 + sqrt(2) + sqrt(2)) / 3 = 2*sqrt(2) / 3
|
||||
const float expected{ 2.F * std::sqrt(2.F) / 3.F };
|
||||
|
||||
EXPECT_NEAR(result, expected, epsilon);
|
||||
}
|
||||
|
||||
TEST(ChamferDistance, negativeCoordinates)
|
||||
{
|
||||
const Vector<2, float> A[]{ { -1.F, -1.F } };
|
||||
const Vector<2, float> B[]{ { 1.F, 1.F } };
|
||||
|
||||
const float result{ chamferDistanceAtoB<SimpleDistance<2>>(A, B) };
|
||||
|
||||
const float expected{ std::sqrt(8.F) }; // sqrt(2^2 + 2^2)
|
||||
EXPECT_NEAR(result, expected, epsilon);
|
||||
}
|
||||
|
||||
TEST(ChamferDistance, higherDimensions)
|
||||
{
|
||||
const Vector<5, float> A[]{ { 1.F, 2.F, 3.F, 4.F, 5.F } };
|
||||
const Vector<5, float> B[]{ { 1.F, 2.F, 3.F, 4.F, 5.F } };
|
||||
|
||||
const float result{ chamferDistanceAtoB<SimpleDistance<5>>(A, B) };
|
||||
|
||||
EXPECT_NEAR(result, 0.F, epsilon);
|
||||
}
|
||||
} // namespace lms::math::chamferDistanceTests
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/CosineDistance.hpp"
|
||||
|
||||
namespace lms::math::cosineDistanceTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-6F };
|
||||
|
||||
TEST(CosineDistance, equalVectors)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> b{ 1.F, 2.F, 3.F };
|
||||
|
||||
EXPECT_NEAR(computeCosineDistance(a, b), 0.F, epsilon);
|
||||
}
|
||||
|
||||
TEST(CosineDistance, orthogonalVectors)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 0.F, 0.F };
|
||||
const Vector<3, float> b{ 0.F, 1.F, 0.F };
|
||||
|
||||
EXPECT_NEAR(computeCosineDistance(a, b), 1.F, epsilon);
|
||||
}
|
||||
|
||||
TEST(CosineDistance, oppositeVectors)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> b{ -1.F, -2.F, -3.F };
|
||||
|
||||
EXPECT_NEAR(computeCosineDistance(a, b), 2.F, epsilon);
|
||||
}
|
||||
|
||||
TEST(CosineDistance, zeroNormVector)
|
||||
{
|
||||
const Vector<3, float> a{ 0.F, 0.F, 0.F };
|
||||
const Vector<3, float> b{ 1.F, 2.F, 3.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(computeCosineDistance(a, b), 1.F);
|
||||
}
|
||||
|
||||
TEST(CosineDistance, vectorMethod)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> b{ 1.F, 2.F, 3.F };
|
||||
|
||||
EXPECT_NEAR(computeCosineDistance(a, b), 0.F, epsilon);
|
||||
}
|
||||
|
||||
TEST(CosineDistance, functor)
|
||||
{
|
||||
const Vector<3, float> reference{ 1.F, 0.F, 0.F };
|
||||
const Vector<3, float> candidate{ 0.F, 1.F, 0.F };
|
||||
const CosineDistance<3, float> distance{ reference };
|
||||
|
||||
EXPECT_NEAR(distance(candidate), 1.F, epsilon);
|
||||
}
|
||||
} // namespace lms::math::cosineDistanceTests
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/CovarianceCalculator.hpp"
|
||||
#include "math/SquareMatrix.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math::covarianceCalculatorTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-6F };
|
||||
|
||||
TEST(CovarianceCalculator, empty)
|
||||
{
|
||||
CovarianceMatrixCalculator<2, float> calculator;
|
||||
|
||||
EXPECT_TRUE(calculator.empty());
|
||||
EXPECT_EQ(calculator.count(), 0U);
|
||||
}
|
||||
|
||||
TEST(CovarianceCalculator, sampleCovariance)
|
||||
{
|
||||
CovarianceMatrixCalculator<2, float> calculator;
|
||||
calculator.add({ 1.0F, 0.0F });
|
||||
calculator.add({ -1.0F, 0.0F });
|
||||
|
||||
SquareMatrix<float, 2> covariance;
|
||||
calculator.finalizeSample(covariance);
|
||||
|
||||
EXPECT_NEAR(covariance[0][0], 2.0F, epsilon);
|
||||
EXPECT_NEAR(covariance[0][1], 0.0F, epsilon);
|
||||
EXPECT_NEAR(covariance[1][0], 0.0F, epsilon);
|
||||
EXPECT_NEAR(covariance[1][1], 0.0F, epsilon);
|
||||
}
|
||||
|
||||
TEST(CovarianceCalculator, populationCovariance)
|
||||
{
|
||||
CovarianceMatrixCalculator<2, float> calculator;
|
||||
calculator.add(Vector<2, float>{ 1.0F, 0.0F });
|
||||
calculator.add(Vector<2, float>{ -1.0F, 0.0F });
|
||||
|
||||
SquareMatrix<float, 2> covariance;
|
||||
calculator.finalizePopulation(covariance);
|
||||
|
||||
EXPECT_NEAR(covariance[0][0], 1.0F, epsilon);
|
||||
EXPECT_NEAR(covariance[0][1], 0.0F, epsilon);
|
||||
EXPECT_NEAR(covariance[1][0], 0.0F, epsilon);
|
||||
EXPECT_NEAR(covariance[1][1], 0.0F, epsilon);
|
||||
}
|
||||
|
||||
TEST(CovarianceCalculator, singleValueReturnsZero)
|
||||
{
|
||||
CovarianceMatrixCalculator<2, float> calculator;
|
||||
calculator.add({ 1.0F, 2.0F });
|
||||
|
||||
SquareMatrix<float, 2> covariance;
|
||||
calculator.finalizeSample(covariance);
|
||||
|
||||
EXPECT_FLOAT_EQ(covariance[0][0], 0.0F);
|
||||
EXPECT_FLOAT_EQ(covariance[0][1], 0.0F);
|
||||
EXPECT_FLOAT_EQ(covariance[1][0], 0.0F);
|
||||
EXPECT_FLOAT_EQ(covariance[1][1], 0.0F);
|
||||
}
|
||||
} // namespace lms::math::covarianceCalculatorTests
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/DotProduct.hpp"
|
||||
|
||||
namespace lms::math::dotProductTests
|
||||
{
|
||||
TEST(DotProduct, zeroLength)
|
||||
{
|
||||
const Vector<0, float> a{};
|
||||
const Vector<0, float> b{};
|
||||
|
||||
EXPECT_FLOAT_EQ(computeDotProduct(a, b), 0.F);
|
||||
}
|
||||
|
||||
TEST(DotProduct, simpleValues)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> b{ 4.F, 5.F, 6.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(computeDotProduct(a, b), 32.F);
|
||||
}
|
||||
|
||||
TEST(DotProduct, orthogonalVectors)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 0.F, 0.F };
|
||||
const Vector<3, float> b{ 0.F, 1.F, 0.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(computeDotProduct(a, b), 0.F);
|
||||
}
|
||||
|
||||
TEST(DotProduct, negativeValues)
|
||||
{
|
||||
const Vector<3, float> a{ -1.F, 2.F, -3.F };
|
||||
const Vector<3, float> b{ 4.F, -5.F, 6.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(computeDotProduct(a, b), -32.F);
|
||||
}
|
||||
|
||||
TEST(DotProduct, vectorMethod)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> b{ 4.F, 5.F, 6.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(computeDotProduct(a, b), 32.F);
|
||||
}
|
||||
|
||||
TEST(DotProduct, functor)
|
||||
{
|
||||
const Vector<3, float> reference{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> candidate{ 4.F, 5.F, 6.F };
|
||||
const DotProduct<3, float> dotProduct{ reference };
|
||||
|
||||
EXPECT_FLOAT_EQ(dotProduct(candidate), 32.F);
|
||||
}
|
||||
} // namespace lms::math::dotProductTests
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/Entropy.hpp"
|
||||
|
||||
namespace lms::math
|
||||
{
|
||||
TEST(EntropyTest, ZeroInput)
|
||||
{
|
||||
std::array<float, 12> c{};
|
||||
|
||||
const float e{ entropy<float>(c) };
|
||||
EXPECT_EQ(e, 0.f);
|
||||
}
|
||||
|
||||
TEST(EntropyTest, SingleBinIsZeroEntropy)
|
||||
{
|
||||
std::array<float, 12> c{};
|
||||
c[3] = 1.F;
|
||||
|
||||
const float e{ entropy<float>(c) };
|
||||
EXPECT_FLOAT_EQ(e, 0.F);
|
||||
}
|
||||
|
||||
TEST(EntropyTest, UniformDistributionMaxEntropy)
|
||||
{
|
||||
std::array<float, 12> c;
|
||||
|
||||
for (auto& v : c)
|
||||
v = 1.F;
|
||||
|
||||
const float e{ entropy<float>(c) };
|
||||
const float expected{ std::log(12.f) };
|
||||
EXPECT_FLOAT_EQ(e, expected);
|
||||
}
|
||||
|
||||
TEST(EntropyTest, ScaleInvariance)
|
||||
{
|
||||
std::array<float, 12> c{ 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f };
|
||||
|
||||
const float a{ entropy<float>(c) };
|
||||
|
||||
for (auto& v : c)
|
||||
v *= 1000.f;
|
||||
|
||||
const float b{ entropy<float>(c) };
|
||||
|
||||
EXPECT_FLOAT_EQ(a, b);
|
||||
}
|
||||
|
||||
TEST(EntropyTest, MoreSpreadMeansHigherEntropy)
|
||||
{
|
||||
std::array<float, 12> tight{};
|
||||
std::array<float, 12> spread{};
|
||||
|
||||
tight[5] = 0.5F;
|
||||
tight[6] = 0.5F;
|
||||
|
||||
spread[2] = 0.3F;
|
||||
spread[6] = 0.4F;
|
||||
spread[9] = 0.3F;
|
||||
|
||||
EXPECT_GT(entropy<float>(spread), entropy<float>(tight));
|
||||
}
|
||||
} // namespace lms::math
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/EuclideanDistance.hpp"
|
||||
|
||||
namespace lms::math::euclideanDistanceTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-4F };
|
||||
|
||||
TEST(EuclideanDistance, zeroLength)
|
||||
{
|
||||
const Vector<0, float> a{};
|
||||
const Vector<0, float> b{};
|
||||
const Vector<0, float> weights{};
|
||||
|
||||
EXPECT_FLOAT_EQ(computeEuclideanSquaredDistance(a, b), 0.F);
|
||||
EXPECT_FLOAT_EQ(computeEuclideanSquaredDistanceWithWeights(a, b, weights), 0.F);
|
||||
}
|
||||
|
||||
TEST(EuclideanDistance, equalVectors)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> b{ 1.F, 2.F, 3.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(computeEuclideanSquaredDistance(a, b), 0.F);
|
||||
}
|
||||
|
||||
TEST(EuclideanDistance, unweightedDistance)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> b{ 4.F, 6.F, 8.F };
|
||||
|
||||
const float expected{ 50.F }; // 3^2 + 4^2 + 5^2
|
||||
EXPECT_NEAR(computeEuclideanSquaredDistance(a, b), expected, epsilon);
|
||||
}
|
||||
|
||||
TEST(EuclideanDistance, weightedDistance)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 3.F, 5.F };
|
||||
const Vector<3, float> b{ 2.F, 1.F, 6.F };
|
||||
const Vector<3, float> weights{ 1.F, 0.5F, 2.F };
|
||||
|
||||
const float expected{ 5.F }; // 1*1 + 4*0.5 + 1*2
|
||||
EXPECT_NEAR(computeEuclideanSquaredDistanceWithWeights(a, b, weights), expected, epsilon);
|
||||
}
|
||||
|
||||
TEST(EuclideanDistance, largeMagnitudeValues)
|
||||
{
|
||||
// 1e15^2 * 2 = 2e30, well within the float max (~3.4e38), so no overflow
|
||||
const float big{ 1e15F };
|
||||
const Vector<2, float> a{ big, big };
|
||||
const Vector<2, float> b{ 0.F, 0.F };
|
||||
|
||||
const float result{ computeEuclideanSquaredDistance(a, b) };
|
||||
EXPECT_GT(result, 0.F);
|
||||
}
|
||||
|
||||
TEST(EuclideanDistance, smallMagnitudeValues)
|
||||
{
|
||||
// Subnormal inputs; result must stay non-negative
|
||||
const float tiny{ std::numeric_limits<float>::min() };
|
||||
const Vector<3, float> a{ tiny, tiny, tiny };
|
||||
const Vector<3, float> b{ 0.F, 0.F, 0.F };
|
||||
|
||||
const float result{ computeEuclideanSquaredDistance(a, b) };
|
||||
EXPECT_GE(result, 0.F);
|
||||
}
|
||||
|
||||
TEST(EuclideanDistance, negativeValues)
|
||||
{
|
||||
// Negative components must produce the same result as their positive mirror
|
||||
const Vector<3, float> a{ -1.F, -2.F, -3.F };
|
||||
const Vector<3, float> b{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> aMirror{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> bMirror{ -1.F, -2.F, -3.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(
|
||||
computeEuclideanSquaredDistance(a, b),
|
||||
computeEuclideanSquaredDistance(aMirror, bMirror));
|
||||
}
|
||||
|
||||
TEST(EuclideanDistance, zeroWeights)
|
||||
{
|
||||
const Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
const Vector<3, float> b{ 4.F, 5.F, 6.F };
|
||||
const Vector<3, float> weights{ 0.F, 0.F, 0.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(computeEuclideanSquaredDistanceWithWeights(a, b, weights), 0.F);
|
||||
}
|
||||
|
||||
TEST(EuclideanDistance, singleElement)
|
||||
{
|
||||
const Vector<1, float> a{ 3.F };
|
||||
const Vector<1, float> b{ 7.F };
|
||||
|
||||
EXPECT_FLOAT_EQ(computeEuclideanSquaredDistance(a, b), 16.F);
|
||||
}
|
||||
} // namespace lms::math::euclideanDistanceTests
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <numbers>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "core/AlignedHeapArray.hpp"
|
||||
|
||||
#include "math/FFT.hpp"
|
||||
#include "math/Window.hpp"
|
||||
|
||||
namespace lms::math::fftTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-3F };
|
||||
|
||||
namespace
|
||||
{
|
||||
std::size_t getRealFFTOutputSize(std::size_t inputSize)
|
||||
{
|
||||
return inputSize / 2 + 1;
|
||||
}
|
||||
|
||||
std::vector<std::complex<float>> computeRealDFT(const std::vector<float>& input)
|
||||
{
|
||||
const std::size_t N{ input.size() };
|
||||
std::vector<std::complex<float>> output(getRealFFTOutputSize(N));
|
||||
|
||||
for (std::size_t k{}; k <= N / 2; ++k)
|
||||
{
|
||||
std::complex<double> sum{ 0.0, 0.0 };
|
||||
for (std::size_t n{}; n < N; ++n)
|
||||
{
|
||||
const double angle{ -2.0 * std::numbers::pi_v<double> * static_cast<double>(k) * static_cast<double>(n) / static_cast<double>(N) };
|
||||
std::complex<double> w{ std::cos(angle), std::sin(angle) };
|
||||
sum += static_cast<double>(input[n]) * w;
|
||||
}
|
||||
output[k] = { static_cast<float>(sum.real()), static_cast<float>(sum.imag()) };
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(FFT, impulse)
|
||||
{
|
||||
constexpr std::size_t N{ 8 };
|
||||
const std::initializer_list<float> inputSignal{ 1.F, 0.F, 0.F, 0.F, 0.F, 0.F, 0.F, 0.F };
|
||||
const auto expected{ computeRealDFT(inputSignal) };
|
||||
|
||||
FixedRealFFTPlan<N> plan;
|
||||
|
||||
core::AlignedHeapArray<float, FixedRealFFTPlan<N>::minBufferAlignment> input{ N };
|
||||
core::AlignedHeapArray<std::complex<float>, FixedRealFFTPlan<N>::minBufferAlignment> output{ getRealFFTOutputSize(N) };
|
||||
|
||||
std::copy(inputSignal.begin(), inputSignal.end(), input.begin());
|
||||
plan.apply(input, output);
|
||||
for (std::size_t i{}; i < output.size(); ++i)
|
||||
{
|
||||
EXPECT_NEAR(output[i].real(), expected[i].real(), epsilon);
|
||||
EXPECT_NEAR(output[i].imag(), expected[i].imag(), epsilon);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FFT, realForwardMatchesReference)
|
||||
{
|
||||
constexpr std::size_t N{ 64 };
|
||||
|
||||
std::vector<float> inputSignal(N);
|
||||
for (std::size_t i{}; i < N; ++i)
|
||||
{
|
||||
inputSignal[i] = std::sin(2.F * std::numbers::pi_v<float> * static_cast<float>(i) / static_cast<float>(N))
|
||||
+ 0.25F * std::sin(6.F * std::numbers::pi_v<float> * static_cast<float>(i) / static_cast<float>(N));
|
||||
}
|
||||
|
||||
const auto expected{ computeRealDFT(inputSignal) };
|
||||
|
||||
FixedRealFFTPlan<N> plan;
|
||||
core::AlignedHeapArray<float, FixedRealFFTPlan<N>::minBufferAlignment> input{ N };
|
||||
core::AlignedHeapArray<std::complex<float>, FixedRealFFTPlan<N>::minBufferAlignment> output{ getRealFFTOutputSize(N) };
|
||||
std::copy(inputSignal.begin(), inputSignal.end(), input.begin());
|
||||
|
||||
plan.apply({ input.data(), input.size() }, { output.data(), output.size() });
|
||||
|
||||
for (std::size_t i{}; i < output.size(); ++i)
|
||||
{
|
||||
EXPECT_NEAR(output[i].real(), expected[i].real(), epsilon);
|
||||
EXPECT_NEAR(output[i].imag(), expected[i].imag(), epsilon);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FFT, singleFrequencyBin)
|
||||
{
|
||||
constexpr std::size_t N{ 64 };
|
||||
|
||||
for (std::size_t k{ 1 }; k < N / 2; ++k)
|
||||
{
|
||||
std::vector<float> inputSignal(N);
|
||||
for (std::size_t n{}; n < N; ++n)
|
||||
inputSignal[n] = std::sin(2.F * std::numbers::pi_v<float> * static_cast<float>(k) * static_cast<float>(n) / static_cast<float>(N));
|
||||
|
||||
const auto expected{ computeRealDFT(inputSignal) };
|
||||
|
||||
FixedRealFFTPlan<N> plan;
|
||||
core::AlignedHeapArray<float, FixedRealFFTPlan<N>::minBufferAlignment> input{ N };
|
||||
core::AlignedHeapArray<std::complex<float>, FixedRealFFTPlan<N>::minBufferAlignment> output{ getRealFFTOutputSize(N) };
|
||||
std::copy(inputSignal.begin(), inputSignal.end(), input.begin());
|
||||
|
||||
plan.apply({ input.data(), input.size() }, { output.data(), output.size() });
|
||||
|
||||
for (std::size_t i{}; i < output.size(); ++i)
|
||||
{
|
||||
if (i == k)
|
||||
EXPECT_GT(std::abs(output[i]), 10.F);
|
||||
else
|
||||
EXPECT_NEAR(std::abs(output[i]), std::abs(expected[i]), epsilon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FFT, forwardIsUnnormalized)
|
||||
{
|
||||
constexpr std::size_t N{ 64 };
|
||||
|
||||
FixedRealFFTPlan<N> plan;
|
||||
core::AlignedHeapArray<float, FixedRealFFTPlan<N>::minBufferAlignment> input{ N };
|
||||
core::AlignedHeapArray<std::complex<float>, FixedRealFFTPlan<N>::minBufferAlignment> output{ getRealFFTOutputSize(N) };
|
||||
std::fill(input.begin(), input.end(), 1.F);
|
||||
|
||||
plan.apply({ input.data(), input.size() }, { output.data(), output.size() });
|
||||
|
||||
EXPECT_NEAR(output[0].real(), static_cast<float>(N), epsilon);
|
||||
}
|
||||
|
||||
TEST(FFT, parseval)
|
||||
{
|
||||
constexpr std::size_t N{ 64 };
|
||||
|
||||
std::vector<float> inputSignal(N);
|
||||
for (std::size_t i{}; i < N; ++i)
|
||||
inputSignal[i] = std::sin(static_cast<float>(i));
|
||||
|
||||
float timeEnergy{};
|
||||
for (const auto value : inputSignal)
|
||||
timeEnergy += value * value;
|
||||
|
||||
FixedRealFFTPlan<N> plan;
|
||||
core::AlignedHeapArray<float, FixedRealFFTPlan<N>::minBufferAlignment> input{ N };
|
||||
core::AlignedHeapArray<std::complex<float>, FixedRealFFTPlan<N>::minBufferAlignment> output{ getRealFFTOutputSize(N) };
|
||||
std::copy(inputSignal.begin(), inputSignal.end(), input.begin());
|
||||
|
||||
plan.apply({ input.data(), input.size() }, { output.data(), output.size() });
|
||||
|
||||
float freqEnergy{};
|
||||
freqEnergy += std::norm(output[0]);
|
||||
freqEnergy += std::norm(output[N / 2]);
|
||||
for (std::size_t k{ 1 }; k < N / 2; ++k)
|
||||
freqEnergy += 2.F * std::norm(output[k]);
|
||||
|
||||
EXPECT_NEAR(timeEnergy, freqEnergy / static_cast<float>(N), epsilon);
|
||||
}
|
||||
|
||||
TEST(FFT, parsevalWithWindow)
|
||||
{
|
||||
constexpr std::size_t N{ 64 };
|
||||
|
||||
std::vector<float> inputSignal(N);
|
||||
for (std::size_t n{}; n < N; ++n)
|
||||
inputSignal[n] = std::sin(2.F * std::numbers::pi_v<float> * static_cast<float>(n) / static_cast<float>(N));
|
||||
|
||||
const math::HannWindow<N, float> window;
|
||||
const float windowEnergy{ window.energy() };
|
||||
|
||||
std::vector<float> windowedInput(N);
|
||||
window.apply(std::span<const float, N>{ inputSignal.data(), inputSignal.size() },
|
||||
std::span<float, N>{ windowedInput.data(), windowedInput.size() });
|
||||
|
||||
float E_time{};
|
||||
for (float x : windowedInput)
|
||||
E_time += x * x;
|
||||
E_time /= windowEnergy;
|
||||
|
||||
FixedRealFFTPlan<N> plan;
|
||||
core::AlignedHeapArray<float, FixedRealFFTPlan<N>::minBufferAlignment> input{ N };
|
||||
core::AlignedHeapArray<std::complex<float>, FixedRealFFTPlan<N>::minBufferAlignment> output{ getRealFFTOutputSize(N) };
|
||||
std::copy(windowedInput.begin(), windowedInput.end(), input.begin());
|
||||
plan.apply({ input.data(), input.size() }, { output.data(), output.size() });
|
||||
|
||||
float E_freq{};
|
||||
E_freq += std::norm(output[0]);
|
||||
E_freq += std::norm(output[N / 2]);
|
||||
for (std::size_t k{ 1 }; k < N / 2; ++k)
|
||||
E_freq += 2.F * std::norm(output[k]);
|
||||
E_freq /= (windowEnergy * static_cast<float>(N));
|
||||
|
||||
EXPECT_NEAR(E_time, E_freq, epsilon * E_time) << "Time-domain and frequency-domain energy mismatch after windowing";
|
||||
}
|
||||
} // namespace lms::math::fftTests
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/MedoidCalculator.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math::medoidCalculatorTests
|
||||
{
|
||||
TEST(MedoidCalculator, initialState)
|
||||
{
|
||||
MedoidCalculator<Vector<3, float>> calculator;
|
||||
|
||||
EXPECT_TRUE(calculator.empty());
|
||||
EXPECT_EQ(calculator.count(), 0U);
|
||||
}
|
||||
|
||||
TEST(MedoidCalculator, singleVector)
|
||||
{
|
||||
MedoidCalculator<Vector<3, float>> calculator;
|
||||
const Vector<3, float> vec{ 1.0F, 2.0F, 3.0F };
|
||||
calculator.add(vec);
|
||||
|
||||
EXPECT_FALSE(calculator.empty());
|
||||
EXPECT_EQ(calculator.count(), 1U);
|
||||
EXPECT_EQ(calculator.findMedoidIndex(), 0U);
|
||||
|
||||
const Vector<3, float> result = calculator.finalize();
|
||||
EXPECT_EQ(result[0], 1.0F);
|
||||
EXPECT_EQ(result[1], 2.0F);
|
||||
EXPECT_EQ(result[2], 3.0F);
|
||||
}
|
||||
|
||||
TEST(MedoidCalculator, twoVectors)
|
||||
{
|
||||
MedoidCalculator<Vector<2, float>> calculator;
|
||||
const Vector<2, float> v1{ 0.0F, 0.0F };
|
||||
const Vector<2, float> v2{ 4.0F, 0.0F };
|
||||
|
||||
calculator.add(v1);
|
||||
calculator.add(v2);
|
||||
|
||||
EXPECT_EQ(calculator.count(), 2U);
|
||||
// Both have equal distance to the other, but first one is returned
|
||||
const std::size_t medoidIndex = calculator.findMedoidIndex();
|
||||
EXPECT_TRUE(medoidIndex == 0 || medoidIndex == 1);
|
||||
}
|
||||
|
||||
TEST(MedoidCalculator, threeDifferentVectors)
|
||||
{
|
||||
MedoidCalculator<Vector<2, float>> calculator;
|
||||
// Three points: (0,0), (1,0), (10,0)
|
||||
// Medoid should be (1,0) as it's closest to the others
|
||||
calculator.add(Vector<2, float>{ 0.0F, 0.0F });
|
||||
calculator.add(Vector<2, float>{ 1.0F, 0.0F });
|
||||
calculator.add(Vector<2, float>{ 10.0F, 0.0F });
|
||||
|
||||
const std::size_t medoidIndex = calculator.findMedoidIndex();
|
||||
EXPECT_EQ(medoidIndex, 1U); // The middle point (1,0) is the medoid
|
||||
|
||||
const Vector<2, float> result = calculator.finalize();
|
||||
EXPECT_FLOAT_EQ(result[0], 1.0F);
|
||||
EXPECT_FLOAT_EQ(result[1], 0.0F);
|
||||
}
|
||||
|
||||
TEST(MedoidCalculator, computeMedoidSpan)
|
||||
{
|
||||
const std::array<Vector<2, float>, 3> values{
|
||||
Vector<2, float>{ 0.0F, 0.0F },
|
||||
Vector<2, float>{ 1.0F, 0.0F },
|
||||
Vector<2, float>{ 10.0F, 0.0F }
|
||||
};
|
||||
|
||||
const Vector<2, float> result = computeMedoid(std::span<const Vector<2, float>>(values));
|
||||
|
||||
EXPECT_FLOAT_EQ(result[0], 1.0F);
|
||||
EXPECT_FLOAT_EQ(result[1], 0.0F);
|
||||
}
|
||||
|
||||
TEST(MedoidCalculator, getVector)
|
||||
{
|
||||
MedoidCalculator<Vector<2, float>> calculator;
|
||||
calculator.add(Vector<2, float>{ 1.0F, 2.0F });
|
||||
calculator.add(Vector<2, float>{ 3.0F, 4.0F });
|
||||
|
||||
const Vector<2, float>& v0 = calculator.getVector(0U);
|
||||
const Vector<2, float>& v1 = calculator.getVector(1U);
|
||||
|
||||
EXPECT_FLOAT_EQ(v0[0], 1.0F);
|
||||
EXPECT_FLOAT_EQ(v0[1], 2.0F);
|
||||
EXPECT_FLOAT_EQ(v1[0], 3.0F);
|
||||
EXPECT_FLOAT_EQ(v1[1], 4.0F);
|
||||
}
|
||||
|
||||
TEST(MedoidCalculator, clear)
|
||||
{
|
||||
MedoidCalculator<Vector<2, float>> calculator;
|
||||
calculator.add(Vector<2, float>{ 1.0F, 2.0F });
|
||||
EXPECT_EQ(calculator.count(), 1);
|
||||
calculator.clear();
|
||||
EXPECT_EQ(calculator.count(), 0);
|
||||
}
|
||||
} // namespace lms::math::medoidCalculatorTests
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/NormalizedCosineDistance.hpp"
|
||||
|
||||
namespace lms::math::normalizedCosineDistanceTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-6F };
|
||||
|
||||
TEST(NormalizedCosineDistance, equalNormalizedVectors)
|
||||
{
|
||||
Vector<3, float> a{ 1.F, 2.F, 3.F };
|
||||
Vector<3, float> b{ 1.F, 2.F, 3.F };
|
||||
|
||||
a.normalizeL2();
|
||||
b.normalizeL2();
|
||||
|
||||
EXPECT_NEAR(computeNormalizedCosineDistance(a, b), 0.F, epsilon);
|
||||
}
|
||||
|
||||
TEST(NormalizedCosineDistance, orthogonalNormalizedVectors)
|
||||
{
|
||||
Vector<3, float> a{ 1.F, 0.F, 0.F };
|
||||
Vector<3, float> b{ 0.F, 1.F, 0.F };
|
||||
|
||||
a.normalizeL2();
|
||||
b.normalizeL2();
|
||||
|
||||
EXPECT_NEAR(computeNormalizedCosineDistance(a, b), 0.5F, epsilon);
|
||||
}
|
||||
|
||||
TEST(NormalizedCosineDistance, oppositeNormalizedVectors)
|
||||
{
|
||||
Vector<3, float> a{ 1.F, 1.F, 0.F };
|
||||
Vector<3, float> b{ -1.F, -1.F, 0.F };
|
||||
|
||||
a.normalizeL2();
|
||||
b.normalizeL2();
|
||||
|
||||
EXPECT_NEAR(computeNormalizedCosineDistance(a, b), 1.F, epsilon);
|
||||
}
|
||||
|
||||
TEST(NormalizedCosineDistance, functor)
|
||||
{
|
||||
Vector<3, float> reference{ 1.F, 0.F, 0.F };
|
||||
Vector<3, float> candidate{ 0.F, 1.F, 0.F };
|
||||
|
||||
reference.normalizeL2();
|
||||
candidate.normalizeL2();
|
||||
|
||||
const NormalizedCosineDistance<3, float> distance{ reference };
|
||||
|
||||
EXPECT_NEAR(distance(candidate), 0.5F, epsilon);
|
||||
}
|
||||
} // namespace lms::math::normalizedCosineDistanceTests
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/PrincipalComponents.hpp"
|
||||
|
||||
namespace lms::math::principalComponentsTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-4F };
|
||||
constexpr float doubleEpsilon{ 1e-8 };
|
||||
|
||||
TEST(PrincipalComponents, dotProductZeroVectors)
|
||||
{
|
||||
Vector<3, float> a{ 0.0F, 0.0F, 0.0F };
|
||||
Vector<3, float> b{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(dotProduct(a, b), 0.0F);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, dotProductOrthogonal)
|
||||
{
|
||||
Vector<3, float> a{ 1.0F, 0.0F, 0.0F };
|
||||
Vector<3, float> b{ 0.0F, 1.0F, 0.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(dotProduct(a, b), 0.0F);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, dotProductParallel)
|
||||
{
|
||||
Vector<3, float> a{ 1.0F, 2.0F, 3.0F };
|
||||
Vector<3, float> b{ 2.0F, 4.0F, 6.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(dotProduct(a, b), 28.0F); // 2 + 8 + 18
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, dotProductAntiparallel)
|
||||
{
|
||||
Vector<3, float> a{ 1.0F, 2.0F, 3.0F };
|
||||
Vector<3, float> b{ -1.0F, -2.0F, -3.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(dotProduct(a, b), -14.0F);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, dotProductDouble)
|
||||
{
|
||||
Vector<3, double> a{ 0.5, 0.5, 0.5 };
|
||||
Vector<3, double> b{ 2.0, 2.0, 2.0 };
|
||||
|
||||
EXPECT_DOUBLE_EQ(dotProduct(a, b), 3.0);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, pearsonCorrelationIdentical)
|
||||
{
|
||||
Vector<5, float> a{ 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };
|
||||
Vector<5, float> b{ 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };
|
||||
|
||||
EXPECT_NEAR(pearsonCorrelation(a, b), 1.0F, epsilon);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, pearsonCorrelationNegative)
|
||||
{
|
||||
Vector<5, float> a{ 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };
|
||||
Vector<5, float> b{ 5.0F, 4.0F, 3.0F, 2.0F, 1.0F };
|
||||
|
||||
EXPECT_NEAR(pearsonCorrelation(a, b), -1.0F, epsilon);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, pearsonCorrelationIndependent)
|
||||
{
|
||||
Vector<4, float> a{ 1.0F, 2.0F, 3.0F, 4.0F };
|
||||
Vector<4, float> b{ 4.0F, 3.0F, 2.0F, 1.0F };
|
||||
|
||||
EXPECT_NEAR(std::abs(pearsonCorrelation(a, b)), 1.0F, epsilon);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, pearsonCorrelationConstantVector)
|
||||
{
|
||||
Vector<5, float> a{ 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };
|
||||
Vector<5, float> b{ 2.0F, 2.0F, 2.0F, 2.0F, 2.0F };
|
||||
|
||||
// Constant vector has zero variance
|
||||
EXPECT_FLOAT_EQ(pearsonCorrelation(a, b), 0.0F);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, pearsonCorrelationBothConstant)
|
||||
{
|
||||
Vector<5, float> a{ 1.0F, 1.0F, 1.0F, 1.0F, 1.0F };
|
||||
Vector<5, float> b{ 2.0F, 2.0F, 2.0F, 2.0F, 2.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(pearsonCorrelation(a, b), 0.0F);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, pearsonCorrelationWeakPositive)
|
||||
{
|
||||
Vector<4, float> a{ 1.0F, 2.0F, 3.0F, 4.0F };
|
||||
Vector<4, float> b{ 1.1F, 2.1F, 2.9F, 3.9F };
|
||||
|
||||
float corr = pearsonCorrelation(a, b);
|
||||
EXPECT_GT(corr, 0.9F);
|
||||
EXPECT_LE(corr, 1.0F);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, powerIterationReturnsEigenpairs)
|
||||
{
|
||||
SquareMatrix<double, 2> covariance;
|
||||
covariance[0][0] = 2.0;
|
||||
covariance[0][1] = 0.0;
|
||||
covariance[1][0] = 0.0;
|
||||
covariance[1][1] = 1.0;
|
||||
|
||||
Vector<2, double> eigenvalues{};
|
||||
std::array<Vector<2, double>, 2> eigenvectors;
|
||||
|
||||
computeEigenpairsViaPowerIteration(covariance, eigenvectors, eigenvalues);
|
||||
|
||||
EXPECT_NEAR(eigenvalues[0], 2.0, epsilon);
|
||||
EXPECT_NEAR(eigenvalues[1], 1.0, epsilon);
|
||||
|
||||
for (std::size_t k{}; k < 2; ++k)
|
||||
{
|
||||
Vector<2, double> Av{};
|
||||
for (std::size_t i{}; i < 2; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j < 2; ++j)
|
||||
Av[i] += covariance[i][j] * eigenvectors[k][j];
|
||||
}
|
||||
|
||||
EXPECT_NEAR(Av[0], eigenvalues[k] * eigenvectors[k][0], epsilon);
|
||||
EXPECT_NEAR(Av[1], eigenvalues[k] * eigenvectors[k][1], epsilon);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, powerIterationIdentity)
|
||||
{
|
||||
SquareMatrix<double, 3> covariance;
|
||||
covariance.fill(0.0);
|
||||
covariance[0][0] = 1.0;
|
||||
covariance[1][1] = 1.0;
|
||||
covariance[2][2] = 1.0;
|
||||
|
||||
Vector<3, double> eigenvalues{};
|
||||
std::array<Vector<3, double>, 3> eigenvectors;
|
||||
|
||||
computeEigenpairsViaPowerIteration(covariance, eigenvectors, eigenvalues);
|
||||
|
||||
// All eigenvalues should be 1
|
||||
EXPECT_NEAR(eigenvalues[0], 1.0, doubleEpsilon);
|
||||
EXPECT_NEAR(eigenvalues[1], 1.0, doubleEpsilon);
|
||||
EXPECT_NEAR(eigenvalues[2], 1.0, doubleEpsilon);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, powerIterationSymmetric)
|
||||
{
|
||||
SquareMatrix<double, 2> covariance;
|
||||
covariance[0][0] = 4.0;
|
||||
covariance[0][1] = 2.0;
|
||||
covariance[1][0] = 2.0;
|
||||
covariance[1][1] = 3.0;
|
||||
|
||||
Vector<2, double> eigenvalues{};
|
||||
std::array<Vector<2, double>, 2> eigenvectors;
|
||||
|
||||
computeEigenpairsViaPowerIteration(covariance, eigenvectors, eigenvalues);
|
||||
|
||||
// Verify A*v = lambda*v for each eigenpair
|
||||
for (std::size_t k{}; k < 2; ++k)
|
||||
{
|
||||
Vector<2, double> Av{};
|
||||
for (std::size_t i{}; i < 2; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j < 2; ++j)
|
||||
Av[i] += covariance[i][j] * eigenvectors[k][j];
|
||||
}
|
||||
|
||||
EXPECT_NEAR(Av[0], eigenvalues[k] * eigenvectors[k][0], epsilon);
|
||||
EXPECT_NEAR(Av[1], eigenvalues[k] * eigenvectors[k][1], epsilon);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, powerIterationWithCustomIterations)
|
||||
{
|
||||
SquareMatrix<double, 2> covariance;
|
||||
covariance[0][0] = 2.0;
|
||||
covariance[0][1] = 0.0;
|
||||
covariance[1][0] = 0.0;
|
||||
covariance[1][1] = 1.0;
|
||||
|
||||
Vector<2, double> eigenvalues{};
|
||||
std::array<Vector<2, double>, 2> eigenvectors;
|
||||
|
||||
// Use only 50 iterations
|
||||
computeEigenpairsViaPowerIteration(covariance, eigenvectors, eigenvalues, 50, 1e-10);
|
||||
|
||||
EXPECT_NEAR(eigenvalues[0], 2.0, 1e-2);
|
||||
EXPECT_NEAR(eigenvalues[1], 1.0, 1e-2);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, powerIterationWithCustomEpsilon)
|
||||
{
|
||||
SquareMatrix<double, 2> covariance;
|
||||
covariance[0][0] = 2.0;
|
||||
covariance[0][1] = 0.0;
|
||||
covariance[1][0] = 0.0;
|
||||
covariance[1][1] = 1.0;
|
||||
|
||||
Vector<2, double> eigenvalues{};
|
||||
std::array<Vector<2, double>, 2> eigenvectors;
|
||||
|
||||
// Use looser epsilon
|
||||
computeEigenpairsViaPowerIteration(covariance, eigenvectors, eigenvalues, 200, 1e-6);
|
||||
|
||||
EXPECT_NEAR(eigenvalues[0], 2.0, epsilon);
|
||||
EXPECT_NEAR(eigenvalues[1], 1.0, epsilon);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, powerIterationLargerMatrix)
|
||||
{
|
||||
// Create a 4x4 diagonal matrix
|
||||
SquareMatrix<double, 4> covariance;
|
||||
covariance.fill(0.0);
|
||||
covariance[0][0] = 4.0;
|
||||
covariance[1][1] = 3.0;
|
||||
covariance[2][2] = 2.0;
|
||||
covariance[3][3] = 1.0;
|
||||
|
||||
Vector<4, double> eigenvalues{};
|
||||
std::array<Vector<4, double>, 4> eigenvectors;
|
||||
|
||||
computeEigenpairsViaPowerIteration(covariance, eigenvectors, eigenvalues);
|
||||
|
||||
// Eigenvalues should be 4, 3, 2, 1 (in descending order after deflation)
|
||||
EXPECT_NEAR(eigenvalues[0], 4.0, doubleEpsilon);
|
||||
EXPECT_NEAR(eigenvalues[1], 3.0, doubleEpsilon);
|
||||
EXPECT_NEAR(eigenvalues[2], 2.0, doubleEpsilon);
|
||||
EXPECT_NEAR(eigenvalues[3], 1.0, doubleEpsilon);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, projectOntoBasis)
|
||||
{
|
||||
std::array<std::array<float, 2>, 2> basis{};
|
||||
basis[0][0] = 1.0F;
|
||||
basis[0][1] = 0.0F;
|
||||
basis[1][0] = 0.0F;
|
||||
basis[1][1] = 1.0F;
|
||||
|
||||
Vector<2, float> centered{ 1.0F, 2.0F };
|
||||
Vector<2, float> output;
|
||||
std::array<float, 2> scales{ 2.0F, 3.0F };
|
||||
|
||||
projectOntoBasis(basis, centered, output, scales);
|
||||
|
||||
EXPECT_FLOAT_EQ(output[0], 2.0F);
|
||||
EXPECT_FLOAT_EQ(output[1], 6.0F);
|
||||
}
|
||||
|
||||
TEST(PrincipalComponents, pearsonCorrelation)
|
||||
{
|
||||
Vector<3, float> a{ 1.0F, 2.0F, 3.0F };
|
||||
Vector<3, float> b{ 1.0F, 2.0F, 3.0F };
|
||||
Vector<3, float> c{ -1.0F, -2.0F, -3.0F };
|
||||
|
||||
EXPECT_NEAR(pearsonCorrelation(a, b), 1.0F, epsilon);
|
||||
EXPECT_NEAR(pearsonCorrelation(a, c), -1.0F, epsilon);
|
||||
}
|
||||
} // namespace lms::math::principalComponentsTests
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/SquareMatrix.hpp"
|
||||
|
||||
namespace lms::math::squareMatrixTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-4F };
|
||||
|
||||
TEST(SquareMatrix, choleskyDecomposePositiveDefinite)
|
||||
{
|
||||
SquareMatrix<float, 3> matrix;
|
||||
matrix.fill(0.F);
|
||||
|
||||
matrix[0][0] = 4.F;
|
||||
matrix[0][1] = 12.F;
|
||||
matrix[0][2] = -16.F;
|
||||
matrix[1][0] = 12.F;
|
||||
matrix[1][1] = 37.F;
|
||||
matrix[1][2] = -43.F;
|
||||
matrix[2][0] = -16.F;
|
||||
matrix[2][1] = -43.F;
|
||||
matrix[2][2] = 98.F;
|
||||
|
||||
SquareMatrix<float, 3> lower;
|
||||
EXPECT_TRUE(choleskyDecompose(matrix, lower));
|
||||
|
||||
EXPECT_FLOAT_EQ(lower[0][0], 2.F);
|
||||
EXPECT_FLOAT_EQ(lower[1][0], 6.F);
|
||||
EXPECT_FLOAT_EQ(lower[1][1], 1.F);
|
||||
EXPECT_FLOAT_EQ(lower[2][0], -8.F);
|
||||
EXPECT_FLOAT_EQ(lower[2][1], 5.F);
|
||||
EXPECT_FLOAT_EQ(lower[2][2], 3.F);
|
||||
}
|
||||
|
||||
TEST(SquareMatrix, choleskyDecomposeIdentity)
|
||||
{
|
||||
SquareMatrix<float, 3> matrix;
|
||||
matrix.fill(0.F);
|
||||
|
||||
matrix[0][0] = 1.F;
|
||||
matrix[1][1] = 1.F;
|
||||
matrix[2][2] = 1.F;
|
||||
|
||||
SquareMatrix<float, 3> lower;
|
||||
EXPECT_TRUE(choleskyDecompose(matrix, lower));
|
||||
|
||||
for (std::size_t i{}; i < 3; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j < 3; ++j)
|
||||
{
|
||||
if (i == j)
|
||||
EXPECT_FLOAT_EQ(lower[i][j], 1.F);
|
||||
else
|
||||
EXPECT_FLOAT_EQ(lower[i][j], 0.F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SquareMatrix, choleskyDecomposeSize1)
|
||||
{
|
||||
SquareMatrix<float, 1> matrix;
|
||||
matrix[0][0] = 9.F;
|
||||
|
||||
SquareMatrix<float, 1> lower;
|
||||
EXPECT_TRUE(choleskyDecompose(matrix, lower));
|
||||
EXPECT_FLOAT_EQ(lower[0][0], 3.F);
|
||||
}
|
||||
|
||||
TEST(SquareMatrix, choleskyDecomposeNonPositiveDefinite)
|
||||
{
|
||||
SquareMatrix<float, 2> matrix;
|
||||
matrix.fill(0.F);
|
||||
|
||||
SquareMatrix<float, 2> lower;
|
||||
EXPECT_FALSE(choleskyDecompose(matrix, lower));
|
||||
}
|
||||
|
||||
TEST(SquareMatrix, invertLowerTriangular)
|
||||
{
|
||||
SquareMatrix<float, 3> lower;
|
||||
lower.fill(0.F);
|
||||
|
||||
lower[0][0] = 2.F;
|
||||
lower[1][0] = 6.F;
|
||||
lower[1][1] = 1.F;
|
||||
lower[2][0] = -8.F;
|
||||
lower[2][1] = 5.F;
|
||||
lower[2][2] = 3.F;
|
||||
|
||||
SquareMatrix<float, 3> inverse;
|
||||
invertLowerTriangular(lower, inverse);
|
||||
|
||||
SquareMatrix<float, 3> identity;
|
||||
identity.fill(0.F);
|
||||
|
||||
for (std::size_t i{}; i < 3; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j < 3; ++j)
|
||||
{
|
||||
float sum = 0.F;
|
||||
for (std::size_t k{}; k < 3; ++k)
|
||||
{
|
||||
sum += lower[i][k] * inverse[k][j];
|
||||
}
|
||||
identity[i][j] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i{}; i < 3; ++i)
|
||||
{
|
||||
for (std::size_t j{}; j < 3; ++j)
|
||||
{
|
||||
if (i == j)
|
||||
EXPECT_NEAR(identity[i][j], 1.F, epsilon);
|
||||
else
|
||||
EXPECT_NEAR(identity[i][j], 0.F, epsilon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SquareMatrix, invertLowerTriangularSize1)
|
||||
{
|
||||
SquareMatrix<float, 1> lower;
|
||||
lower[0][0] = 5.F;
|
||||
|
||||
SquareMatrix<float, 1> inverse;
|
||||
invertLowerTriangular(lower, inverse);
|
||||
|
||||
EXPECT_FLOAT_EQ(inverse[0][0], 0.2F);
|
||||
}
|
||||
|
||||
TEST(SquareMatrix, computeSymmetryMaxDiff)
|
||||
{
|
||||
SquareMatrix<float, 2> matrix;
|
||||
matrix.fill(0.F);
|
||||
|
||||
matrix[0][1] = 1.F;
|
||||
matrix[1][0] = 1.2F;
|
||||
|
||||
EXPECT_NEAR(computeSymmetryMaxDiff(matrix), 0.2F, epsilon);
|
||||
}
|
||||
} // namespace lms::math::squareMatrixTests
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/StatsAccumulator.hpp"
|
||||
|
||||
namespace lms::math::statsAccumulatorTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-4F };
|
||||
|
||||
TEST(StatsAccumulator, initialState)
|
||||
{
|
||||
StatsAccumulator stats;
|
||||
|
||||
EXPECT_EQ(stats.getCount(), 0);
|
||||
EXPECT_FLOAT_EQ(stats.getMean(), 0.F);
|
||||
EXPECT_FLOAT_EQ(stats.getPopulationVariance(), 0.F);
|
||||
EXPECT_FLOAT_EQ(stats.getSampleVariance(), 0.F);
|
||||
EXPECT_FLOAT_EQ(stats.getPopulationStdDev(), 0.F);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, singleValue)
|
||||
{
|
||||
constexpr float value{ 5.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(value);
|
||||
|
||||
EXPECT_EQ(stats.getCount(), 1);
|
||||
EXPECT_FLOAT_EQ(stats.getMean(), value);
|
||||
|
||||
// Variance should be 0 for a single value
|
||||
EXPECT_FLOAT_EQ(stats.getPopulationVariance(), 0.F);
|
||||
EXPECT_FLOAT_EQ(stats.getSampleVariance(), 0.F);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, multipleValuesMean)
|
||||
{
|
||||
constexpr float a{ 2.F };
|
||||
constexpr float b{ 4.F };
|
||||
constexpr float c{ 6.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(a);
|
||||
stats.add(b);
|
||||
stats.add(c);
|
||||
|
||||
EXPECT_EQ(stats.getCount(), 3);
|
||||
EXPECT_FLOAT_EQ(stats.getMean(), b);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, populationVariance)
|
||||
{
|
||||
constexpr float a{ 2.F };
|
||||
constexpr float b{ 4.F };
|
||||
constexpr float c{ 6.F };
|
||||
constexpr float expectedVariance{ 8.F / 3.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(a);
|
||||
stats.add(b);
|
||||
stats.add(c);
|
||||
|
||||
// Population variance = 8 / 3 ≈ 2.6667
|
||||
EXPECT_NEAR(stats.getPopulationVariance(), expectedVariance, epsilon);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, sampleVariance)
|
||||
{
|
||||
constexpr float a{ 2.F };
|
||||
constexpr float b{ 4.F };
|
||||
constexpr float c{ 6.F };
|
||||
constexpr float expectedVariance{ 4.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(a);
|
||||
stats.add(b);
|
||||
stats.add(c);
|
||||
|
||||
// Sample variance = 8 / 2 = 4
|
||||
EXPECT_NEAR(stats.getSampleVariance(), expectedVariance, epsilon);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, standardDeviation)
|
||||
{
|
||||
constexpr float a{ 2.F };
|
||||
constexpr float b{ 4.F };
|
||||
constexpr float c{ 6.F };
|
||||
constexpr float expectedStdDev{ 2.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(a);
|
||||
stats.add(b);
|
||||
stats.add(c);
|
||||
|
||||
// sqrt(4) = 2 (sample stddev)
|
||||
EXPECT_NEAR(stats.getSampleStdDev(), expectedStdDev, epsilon);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, largeMagnitudeValues)
|
||||
{
|
||||
// Welford's algorithm must stay numerically stable with large inputs
|
||||
// 1e6 is within float's ~7 significant-digit range
|
||||
constexpr float big{ 1e6F };
|
||||
constexpr float offset{ 2.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(big);
|
||||
stats.add(big + 1.F);
|
||||
stats.add(big + offset);
|
||||
|
||||
EXPECT_NEAR(stats.getMean(), big + 1.F, 1e-1F);
|
||||
EXPECT_NEAR(stats.getSampleVariance(), 1.F, 1e-1F);
|
||||
EXPECT_NEAR(stats.getSampleStdDev(), 1.F, 1e-1F);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, negativeValues)
|
||||
{
|
||||
constexpr float a{ -6.F };
|
||||
constexpr float b{ -4.F };
|
||||
constexpr float c{ -2.F };
|
||||
constexpr float expectedVariance{ 4.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(a);
|
||||
stats.add(b);
|
||||
stats.add(c);
|
||||
|
||||
EXPECT_NEAR(stats.getMean(), b, epsilon);
|
||||
EXPECT_NEAR(stats.getSampleVariance(), expectedVariance, epsilon);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, mixedSignValues)
|
||||
{
|
||||
constexpr float a{ -1.F };
|
||||
constexpr float b{ 0.F };
|
||||
constexpr float c{ 1.F };
|
||||
constexpr float expectedVariance{ 1.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(a);
|
||||
stats.add(b);
|
||||
stats.add(c);
|
||||
|
||||
EXPECT_NEAR(stats.getMean(), 0.F, epsilon);
|
||||
EXPECT_NEAR(stats.getSampleVariance(), expectedVariance, epsilon);
|
||||
}
|
||||
|
||||
TEST(StatsAccumulator, smallMagnitudeValues)
|
||||
{
|
||||
// Values well within float's normal range; variance must stay non-negative
|
||||
constexpr float tiny{ 1e-30F };
|
||||
constexpr float multiplier2{ 2.F };
|
||||
constexpr float multiplier3{ 3.F };
|
||||
StatsAccumulator stats;
|
||||
stats.add(tiny);
|
||||
stats.add(tiny * multiplier2);
|
||||
stats.add(tiny * multiplier3);
|
||||
|
||||
EXPECT_GE(stats.getSampleVariance(), 0.F);
|
||||
EXPECT_GE(stats.getSampleStdDev(), 0.F);
|
||||
}
|
||||
} // namespace lms::math::statsAccumulatorTests
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
namespace lms::math::vectorTests
|
||||
{
|
||||
constexpr float epsilon{ 1e-5F };
|
||||
|
||||
TEST(Vector, constructionDefault)
|
||||
{
|
||||
Vector<3, float> v;
|
||||
|
||||
EXPECT_FLOAT_EQ(v[0], 0.F);
|
||||
EXPECT_FLOAT_EQ(v[1], 0.F);
|
||||
EXPECT_FLOAT_EQ(v[2], 0.F);
|
||||
}
|
||||
|
||||
TEST(Vector, constructionWithInitValue)
|
||||
{
|
||||
Vector<3, float> v{ 5.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(v[0], 5.0F);
|
||||
EXPECT_FLOAT_EQ(v[1], 5.0F);
|
||||
EXPECT_FLOAT_EQ(v[2], 5.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, constructionWithArgs)
|
||||
{
|
||||
Vector<3, float> v{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(v[0], 1.0F);
|
||||
EXPECT_FLOAT_EQ(v[1], 2.0F);
|
||||
EXPECT_FLOAT_EQ(v[2], 3.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, size)
|
||||
{
|
||||
Vector<3, float> v;
|
||||
EXPECT_EQ(v.getSize(), 3U);
|
||||
}
|
||||
|
||||
TEST(Vector, dataAccess)
|
||||
{
|
||||
Vector<3, float> v{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
const float* data = v.data();
|
||||
EXPECT_FLOAT_EQ(data[0], 1.0F);
|
||||
EXPECT_FLOAT_EQ(data[1], 2.0F);
|
||||
EXPECT_FLOAT_EQ(data[2], 3.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, operatorAddAssign)
|
||||
{
|
||||
Vector<3, float> a{ 1.0F, 2.0F, 3.0F };
|
||||
Vector<3, float> b{ 4.0F, 5.0F, 6.0F };
|
||||
|
||||
a += b;
|
||||
|
||||
EXPECT_FLOAT_EQ(a[0], 5.0F);
|
||||
EXPECT_FLOAT_EQ(a[1], 7.0F);
|
||||
EXPECT_FLOAT_EQ(a[2], 9.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, operatorSubAssign)
|
||||
{
|
||||
Vector<3, float> a{ 4.0F, 5.0F, 6.0F };
|
||||
Vector<3, float> b{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
a -= b;
|
||||
|
||||
EXPECT_FLOAT_EQ(a[0], 3.0F);
|
||||
EXPECT_FLOAT_EQ(a[1], 3.0F);
|
||||
EXPECT_FLOAT_EQ(a[2], 3.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, operatorMulAssign)
|
||||
{
|
||||
Vector<3, float> v{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
v *= 2.0F;
|
||||
|
||||
EXPECT_FLOAT_EQ(v[0], 2.0F);
|
||||
EXPECT_FLOAT_EQ(v[1], 4.0F);
|
||||
EXPECT_FLOAT_EQ(v[2], 6.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, operatorAdd)
|
||||
{
|
||||
Vector<3, float> a{ 1.0F, 2.0F, 3.0F };
|
||||
Vector<3, float> b{ 4.0F, 5.0F, 6.0F };
|
||||
|
||||
Vector<3, float> result = a + b;
|
||||
|
||||
EXPECT_FLOAT_EQ(result[0], 5.0F);
|
||||
EXPECT_FLOAT_EQ(result[1], 7.0F);
|
||||
EXPECT_FLOAT_EQ(result[2], 9.0F);
|
||||
|
||||
// Ensure originals unchanged
|
||||
EXPECT_FLOAT_EQ(a[0], 1.0F);
|
||||
EXPECT_FLOAT_EQ(b[0], 4.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, operatorSub)
|
||||
{
|
||||
Vector<3, float> a{ 4.0F, 5.0F, 6.0F };
|
||||
Vector<3, float> b{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
Vector<3, float> result = a - b;
|
||||
|
||||
EXPECT_FLOAT_EQ(result[0], 3.0F);
|
||||
EXPECT_FLOAT_EQ(result[1], 3.0F);
|
||||
EXPECT_FLOAT_EQ(result[2], 3.0F);
|
||||
|
||||
// Ensure originals unchanged
|
||||
EXPECT_FLOAT_EQ(a[0], 4.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, operatorMulScalarRight)
|
||||
{
|
||||
Vector<3, float> v{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
Vector<3, float> result = v * 2.0F;
|
||||
|
||||
EXPECT_FLOAT_EQ(result[0], 2.0F);
|
||||
EXPECT_FLOAT_EQ(result[1], 4.0F);
|
||||
EXPECT_FLOAT_EQ(result[2], 6.0F);
|
||||
|
||||
// Ensure original unchanged
|
||||
EXPECT_FLOAT_EQ(v[0], 1.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, operatorMulScalarLeft)
|
||||
{
|
||||
Vector<3, float> v{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
Vector<3, float> result = 3.0F * v;
|
||||
|
||||
EXPECT_FLOAT_EQ(result[0], 3.0F);
|
||||
EXPECT_FLOAT_EQ(result[1], 6.0F);
|
||||
EXPECT_FLOAT_EQ(result[2], 9.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, computeNorm)
|
||||
{
|
||||
Vector<3, float> v{ 3.0F, 4.0F, 0.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(v.computeNorm(), 5.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, computeNormZero)
|
||||
{
|
||||
Vector<3, float> v{ 0.0F, 0.0F, 0.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(v.computeNorm(), 0.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, normalizeL2)
|
||||
{
|
||||
Vector<3, float> v{ 3.0F, 4.0F, 0.0F };
|
||||
|
||||
v.normalizeL2();
|
||||
|
||||
EXPECT_NEAR(v.computeNorm(), 1.0F, epsilon);
|
||||
EXPECT_NEAR(v[0], 0.6F, epsilon);
|
||||
EXPECT_NEAR(v[1], 0.8F, epsilon);
|
||||
EXPECT_NEAR(v[2], 0.0F, epsilon);
|
||||
}
|
||||
|
||||
TEST(Vector, normalizeL2ZeroVector)
|
||||
{
|
||||
Vector<3, float> v{ 0.0F, 0.0F, 0.0F };
|
||||
|
||||
v.normalizeL2();
|
||||
|
||||
// Zero vector remains unchanged
|
||||
EXPECT_FLOAT_EQ(v[0], 0.0F);
|
||||
EXPECT_FLOAT_EQ(v[1], 0.0F);
|
||||
EXPECT_FLOAT_EQ(v[2], 0.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, normalizeL2SmallVector)
|
||||
{
|
||||
constexpr float tiny{ 1e-15F };
|
||||
Vector<3, float> v{ tiny, tiny, tiny };
|
||||
|
||||
v.normalizeL2();
|
||||
|
||||
// Small vector remains unchanged due to epsilon check
|
||||
EXPECT_FLOAT_EQ(v[0], tiny);
|
||||
EXPECT_FLOAT_EQ(v[1], tiny);
|
||||
EXPECT_FLOAT_EQ(v[2], tiny);
|
||||
}
|
||||
|
||||
TEST(Vector, iterators)
|
||||
{
|
||||
Vector<3, float> v{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
std::size_t index{};
|
||||
for (float val : v)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(val, static_cast<float>(index + 1));
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Vector, constIterators)
|
||||
{
|
||||
const Vector<3, float> v{ 1.0F, 2.0F, 3.0F };
|
||||
|
||||
std::size_t index{};
|
||||
for (auto it = v.cbegin(); it != v.cend(); ++it)
|
||||
{
|
||||
EXPECT_FLOAT_EQ(*it, static_cast<float>(index + 1));
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Vector, size1)
|
||||
{
|
||||
Vector<1, float> v{ 5.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(v[0], 5.0F);
|
||||
EXPECT_FLOAT_EQ(v.computeNorm(), 5.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, largeSize)
|
||||
{
|
||||
constexpr std::size_t size{ 1000 };
|
||||
Vector<size, float> v{ 1.0F };
|
||||
|
||||
EXPECT_NEAR(v.computeNorm(), std::sqrt(static_cast<float>(size)), 1e-4F);
|
||||
}
|
||||
|
||||
TEST(Vector, negativeValues)
|
||||
{
|
||||
Vector<3, float> v{ -1.0F, -2.0F, -3.0F };
|
||||
|
||||
EXPECT_FLOAT_EQ(v.computeNorm(), std::sqrt(14.0F));
|
||||
}
|
||||
|
||||
TEST(Vector, mixedSignValues)
|
||||
{
|
||||
Vector<3, float> a{ -1.0F, 2.0F, -3.0F };
|
||||
Vector<3, float> b{ 1.0F, -2.0F, 3.0F };
|
||||
const Vector<3, float> sum{ a + b };
|
||||
|
||||
EXPECT_FLOAT_EQ(sum[0], 0.0F);
|
||||
EXPECT_FLOAT_EQ(sum[1], 0.0F);
|
||||
EXPECT_FLOAT_EQ(sum[2], 0.0F);
|
||||
}
|
||||
|
||||
TEST(Vector, doubleType)
|
||||
{
|
||||
Vector<3, double> v{ 1.0, 2.0, 3.0 };
|
||||
|
||||
EXPECT_DOUBLE_EQ(v[0], 1.0);
|
||||
EXPECT_NEAR(v.computeNorm(), std::sqrt(14.0), 1e-15);
|
||||
}
|
||||
} // namespace lms::math::vectorTests
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "math/Window.hpp"
|
||||
|
||||
namespace lms::math::tests
|
||||
{
|
||||
TEST(Window, oneSampleWindowIsFinite)
|
||||
{
|
||||
const HannWindow<1, float> window;
|
||||
const auto values{ window.values() };
|
||||
|
||||
EXPECT_TRUE(std::isfinite(values[0]));
|
||||
EXPECT_GE(values[0], 0.F);
|
||||
EXPECT_LE(values[0], 1.F);
|
||||
EXPECT_FLOAT_EQ(window.energy(), 1.F);
|
||||
}
|
||||
|
||||
TEST(Window, twoSamplesWindow)
|
||||
{
|
||||
const HannWindow<2, float> window;
|
||||
const auto values{ window.values() };
|
||||
|
||||
EXPECT_FLOAT_EQ(values[0], 0.F);
|
||||
EXPECT_FLOAT_EQ(values[1], 0.F);
|
||||
EXPECT_FLOAT_EQ(window.energy(), 0.F);
|
||||
}
|
||||
|
||||
TEST(Window, coefficientsAreFiniteAndInRange)
|
||||
{
|
||||
const HannWindow<17, float> window;
|
||||
|
||||
for (float v : window.values())
|
||||
{
|
||||
EXPECT_TRUE(std::isfinite(v));
|
||||
EXPECT_GE(v, 0.F);
|
||||
EXPECT_LE(v, 1.F);
|
||||
}
|
||||
|
||||
EXPECT_GT(window.energy(), 0.F);
|
||||
}
|
||||
|
||||
TEST(Window, symmetric)
|
||||
{
|
||||
const HannWindow<31, float> window;
|
||||
const auto values{ window.values() };
|
||||
|
||||
for (std::size_t i{}; i < values.size() / 2; ++i)
|
||||
EXPECT_NEAR(values[i], values[values.size() - 1 - i], 1e-6F);
|
||||
}
|
||||
|
||||
TEST(Window, applyUsesPrecomputedCoefficients)
|
||||
{
|
||||
constexpr std::size_t size{ 8 };
|
||||
const HannWindow<size, float> window;
|
||||
|
||||
std::vector<float> input(size);
|
||||
for (std::size_t i{}; i < size; ++i)
|
||||
input[i] = static_cast<float>(i + 1);
|
||||
|
||||
std::vector<float> output(size);
|
||||
window.apply(std::span<const float, size>{ input.data(), input.size() },
|
||||
std::span<float, size>{ output.data(), output.size() });
|
||||
|
||||
const auto coefficients{ window.values() };
|
||||
for (std::size_t i{}; i < size; ++i)
|
||||
EXPECT_FLOAT_EQ(output[i], input[i] * coefficients[i]);
|
||||
}
|
||||
} // namespace lms::math::tests
|
||||
Reference in New Issue
Block a user