From 0365be03e1212d873f41bdd78b9ab8b216276399 Mon Sep 17 00:00:00 2001 From: Owen Green Date: Wed, 26 Nov 2025 14:58:00 +0000 Subject: [PATCH 1/2] NNDSVD: Add seedable randomness and test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also fix two bugs: – logical error: checking for < eps when min had already been set to eps – per paper, random init should be [0,mean/100) not [0, 0.001] (Boutsidis and Gallopoulos 2007) --- include/flucoma/algorithms/public/NNDSVD.hpp | 13 +++++---- tests/CMakeLists.txt | 3 +- tests/algorithms/public/TestNNDSVD.cpp | 30 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 tests/algorithms/public/TestNNDSVD.cpp diff --git a/include/flucoma/algorithms/public/NNDSVD.hpp b/include/flucoma/algorithms/public/NNDSVD.hpp index 5385c941c..be0c0e034 100644 --- a/include/flucoma/algorithms/public/NNDSVD.hpp +++ b/include/flucoma/algorithms/public/NNDSVD.hpp @@ -11,6 +11,7 @@ under the European Union’s Horizon 2020 research and innovation programme #pragma once #include "../util/AlgorithmUtils.hpp" +#include "../util/EigenRandom.hpp" #include "../util/FluidEigenMappings.hpp" #include "../../data/FluidIndex.hpp" #include "../../data/TensorTypes.hpp" @@ -28,7 +29,7 @@ class NNDSVD index process(RealMatrixView X, RealMatrixView W, RealMatrixView H, index minRank = 0, index maxRank = 200, double amount = 0.8, - index method = 0) // 0 - NMF-SVD, 1 NNDSVDar, 2 NNDSVDa 3 NNDSVD + index method = 0, index seed = -1) // 0 - NMF-SVD, 1 NNDSVDar, 2 NNDSVDa 3 NNDSVD { using namespace _impl; using namespace Eigen; @@ -101,14 +102,16 @@ class NNDSVD WT.col(j) = u; // avoid scaling for NMF with normalized W HT.row(j) = lbd * v; } - WT = WT.array().max(epsilon); - HT = HT.array().max(epsilon); + + double mean = XT.mean(); if (method == 1) { auto Wrand = - MatrixXd::Random(WT.rows(), WT.cols()).array().abs() / 100.0; + EigenRandom(WT.rows(), WT.cols(), RandomSeed{seed}, + Range{epsilon, mean * 0.001}); auto Hrand = - MatrixXd::Random(HT.rows(), HT.cols()).array().abs() / 100.0; + EigenRandom(HT.rows(), HT.cols(), RandomSeed{seed}, + Range{epsilon, mean * 0.001}); WT = (WT.array() < epsilon).select(Wrand, WT); HT = (HT.array() < epsilon).select(Hrand, HT); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 74ec9efa3..94edd7ab1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -124,7 +124,7 @@ target_link_libraries(TestEnvelopeGate PRIVATE TestSignals) target_link_libraries(TestTransientSlice PRIVATE TestSignals) add_test_executable(TestEigenRandom algorithms/util/TestEigenRandom.cpp) - +add_test_executable(TestNNDSVD algorithms/public/TestNNDSVD.cpp) include(CTest) include(Catch) @@ -153,5 +153,6 @@ catch_discover_tests(TestBufferedProcess WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" catch_discover_tests(TestMLP WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestKMeans WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) catch_discover_tests(TestEigenRandom WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +catch_discover_tests(TestNNDSVD WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) add_compile_tests("FluidTensor Compilation Tests" data/compile_tests/TestFluidTensor_Compile.cpp) diff --git a/tests/algorithms/public/TestNNDSVD.cpp b/tests/algorithms/public/TestNNDSVD.cpp new file mode 100644 index 000000000..90d63c528 --- /dev/null +++ b/tests/algorithms/public/TestNNDSVD.cpp @@ -0,0 +1,30 @@ +#define CATCH_CONFIG_MAIN +#include +#include +#include +#include + +TEST_CASE("NNDSVD Mode 1 is repeatable with manually set random seed"){ + +using Tensor = fluid::FluidTensor; +using fluid::algorithm::NNDSVD; + +// To test the effect of randomness in NNDSVD mode 1, there must be 0s in the input +Tensor input = {{0,0,0},{0,0,0},{0,0,0}}; + +std::vector Ws(3, Tensor(3,3)); +std::vector Hs(3, Tensor(3,3)); + +NNDSVD algo; + +algo.process(input,Ws[0],Hs[0],2,2,0.8,1, 42); +algo.process(input,Ws[1],Hs[1],2,2,0.8,1, 42); +algo.process(input,Ws[2],Hs[2],2,2,0.8,1, 4672); + +using Catch::Matchers::RangeEquals; + +REQUIRE_THAT(Ws[1],RangeEquals(Ws[0])); +REQUIRE_THAT(Ws[1],!RangeEquals(Ws[2])); +REQUIRE_THAT(Hs[1],RangeEquals(Hs[0])); +REQUIRE_THAT(Hs[1],!RangeEquals(Hs[2])); +} \ No newline at end of file From 4a0cb96d3d3b6102839affebb91bb79c2b48adea Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Fri, 28 Nov 2025 14:28:14 +0100 Subject: [PATCH 2/2] the client update --- include/flucoma/clients/nrt/NMFSeedClient.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/flucoma/clients/nrt/NMFSeedClient.hpp b/include/flucoma/clients/nrt/NMFSeedClient.hpp index 36be7abee..d1c72a63b 100644 --- a/include/flucoma/clients/nrt/NMFSeedClient.hpp +++ b/include/flucoma/clients/nrt/NMFSeedClient.hpp @@ -32,6 +32,7 @@ enum NMFSeedParamIndex { kMaxRank, kCoverage, kMethod, + kRandomSeed, kFFT }; @@ -46,6 +47,7 @@ constexpr auto NMFSeedParams = FloatParam("coverage", "Coverage", 0.5, Min(0), Max(1)), EnumParam("method", "Initialization Method", 0, "NMF-SVD", "NNDSVDar", "NNDSVDa", "NNDSVD"), + LongParam("seed", "Random Seed", -1), FFTParam("fftSettings", "FFT Settings", 1024, -1, -1)); class NMFSeedClient : public FluidBaseClient, public OfflineIn, public OfflineOut @@ -100,9 +102,9 @@ class NMFSeedClient : public FluidBaseClient, public OfflineIn, public OfflineOu auto nndsvd = algorithm::NNDSVD(); - index rank = nndsvd.process(magnitude, outputFilters, outputEnvelopes, - get(), get(), - get(), get()); + index rank = nndsvd.process( + magnitude, outputFilters, outputEnvelopes, get(), + get(), get(), get(), get()); auto filters = BufferAdaptor::Access{get().get()}; Result resizeResult =