8#include "../../../tests/testXWC.hpp"
10#include "../modalGainOpt.hpp"
26namespace modalGainOptTest
44 void readConfigFile(
const std::string &path )
46 config.readConfig( path );
49 int shutdownState()
const
54 float gainGain()
const
59 float gainLeak()
const
64 int extrapMethod()
const
69 int extrapNoiseEstimateDomain()
const
71 return m_extrapNoiseEstimateDomain;
74 int extrapNoiseEstimateRange()
const
76 return m_extrapNoiseEstimateRange;
79 int extrapNoiseEstimateStatistic()
const
81 return m_extrapNoiseEstimateStatistic;
84 int extrapClosedLoopOlEstimateMethod()
const
86 return m_extrapClosedLoopOlEstimateMethod;
89 int extrapPowerLawCrossoverMode()
const
91 return m_extrapPowerLawCrossoverMode;
94 void setExtrapMethodForTest(
int method )
99 void setExtrapNoiseEstimateDomainForTest(
int domain )
101 m_extrapNoiseEstimateDomain = domain;
105 void setExtrapNoiseEstimateRangeForTest(
int range )
107 m_extrapNoiseEstimateRange = range;
111 void setExtrapNoiseEstimateStatisticForTest(
int statistic )
113 m_extrapNoiseEstimateStatistic = statistic;
117 void setExtrapClosedLoopOlEstimateMethodForTest(
int method )
119 m_extrapClosedLoopOlEstimateMethod = method;
123 void setExtrapPowerLawCrossoverModeForTest(
int mode )
125 m_extrapPowerLawCrossoverMode = mode;
131 return m_extrapConfig;
134 bool autoUpdate()
const
144 int sinceChange()
const
146 return m_sinceChange;
149 bool goptUpdated()
const
151 return m_goptUpdated;
154 bool pcgoptUpdated()
const
156 return m_pcgoptUpdated;
159 bool freqUpdated()
const
161 return m_freqUpdated;
169 const std::vector<float> &freq()
const
174 void setPcOnForTest(
bool pcOn )
179 void setLoopForTest(
bool loop )
184 void setModesOnForTest(
int modesOn )
189 void setSinceChangeForTest(
int sinceChange )
191 m_sinceChange = sinceChange;
194 void setGoptUpdatedForTest(
bool goptUpdated )
196 m_goptUpdated = goptUpdated;
199 void setPcgoptUpdatedForTest(
bool pcgoptUpdated )
201 m_pcgoptUpdated = pcgoptUpdated;
204 void setFreqUpdatedForTest(
bool freqUpdated )
206 m_freqUpdated = freqUpdated;
209 void setFpsForTest(
float fps )
214 void setFreqForTest(
const std::vector<float> &freq )
219 void configurePublishedGainState(
const std::vector<float> &gainCalFacts,
220 const std::vector<float> &gainSIRaw,
221 const std::vector<float> &gainSI,
222 const std::vector<float> &gainMaxSI,
223 const std::vector<float> &gainLP,
224 const std::vector<float> &gainMaxLP,
225 const std::vector<float> &gainCals,
226 const std::vector<float> &modeVarOL,
227 const std::vector<float> &modeVarSI,
228 const std::vector<float> &modeVarLP,
231 m_gainCalFacts = gainCalFacts;
232 m_optGainSIRaw = gainSIRaw;
233 m_optGainSI = gainSI;
234 m_gmaxSI = gainMaxSI;
235 m_optGainLP = gainLP;
236 m_gmaxLP = gainMaxLP;
237 m_gainCals = gainCals;
238 m_modeVarOL = modeVarOL;
239 m_modeVarSI = modeVarSI;
240 m_modeVarLP = modeVarLP;
244 void writePublishedGainArraysForTest(
float *currentData,
252 writePublishedGainArrays( currentData, siRawData, siData, maxSiData, lpData, maxLpData, modeVarData );
255 const std::vector<float> &integratedSiGainsForTest()
const
260 void configureSiIntegratorStateForTest(
const std::vector<float> &gainSIRaw,
261 const std::vector<float> &gainSI,
265 m_optGainSIRaw = gainSIRaw;
266 m_optGainSI = gainSI;
271 void updateIntegratedSiGainForTest(
size_t modeIndex )
273 updateIntegratedSiGain( modeIndex );
276 int requestZeroGainsForTest(
bool on =
true )
280 std::fill( m_optGainSI.begin(), m_optGainSI.end(), 0.0F );
281 m_siGainStateNeedsSync =
false;
292 void initZeroGainsPropertyForTest()
294 createStandardIndiRequestSw( m_indiP_zeroGains,
"zero_gains" );
297 void configurePublishedPredictorState(
const std::vector<float> &gainCalFacts,
298 const std::vector<float> &gainLP,
299 const std::vector<float> &gainCals,
300 const std::vector<uint32_t> &Na,
301 const std::vector<uint32_t> &Nb,
302 const std::vector<std::vector<float>> &aCoeff,
303 const std::vector<std::vector<float>> &bCoeff,
307 m_gainCalFacts = gainCalFacts;
308 m_optGainLP = gainLP;
309 m_gainCals = gainCals;
315 m_goptLP.resize( aCoeff.size() );
316 for(
size_t n = 0; n < aCoeff.size(); ++n )
318 m_goptLP[n].a( aCoeff[n] );
319 m_goptLP[n].b( bCoeff[n] );
323 void writePublishedPredictorArraysForTest(
324 float *pcGainData,
float *aCoeffData, uint32_t aWidth,
float *bCoeffData, uint32_t bWidth,
bool blend )
326 writePublishedPredictorArrays( pcGainData, aCoeffData, aWidth, bCoeffData, bWidth, blend );
329 int countEnabledGainFactorsForTest(
const std::vector<float> &gainFacts )
const
331 return countEnabledGainFactors( gainFacts );
334 void updateAppliedModeCountForTest(
const std::vector<float> &gainFacts,
bool predictorPath )
336 updateAppliedModeCount( gainFacts, predictorPath );
339 bool applyGainFactorUpdateForTest( std::vector<float> &gainFacts,
340 const std::vector<float> &incoming,
343 return applyGainFactorUpdate( gainFacts, incoming.data(), incoming.size(), predictorPath );
346 bool applyMultiplierUpdateForTest( std::vector<float> &multFacts,
347 const std::vector<float> &incoming,
350 return applyMultiplierUpdate( multFacts, incoming.data(), incoming.size(), predictorPath );
353 bool applyFrequencyUpdateForTest(
const std::vector<float> &incoming )
355 return applyFrequencyUpdate( incoming.data(), incoming.size() );
358 void configureGoptStructureInputsForTest(
const std::vector<float> &gainFacts,
359 const std::vector<float> &taus,
360 const std::vector<float> &multFacts,
361 const std::vector<float> &freq )
363 m_gainFacts = gainFacts;
365 m_multFacts = multFacts;
367 m_gmaxSI.resize( gainFacts.size(), 0.0F );
370 size_t goptCurrentSize()
const
372 return m_goptCurrent.size();
375 bool refreshGoptStructuresForTest()
377 std::lock_guard<std::mutex>
lock( m_goptMutex );
378 return refreshGoptStructures();
381 void initExtrapSelectionPropertiesForTest()
383 createStandardIndiSelectionSw( m_indiP_extrapMethod,
393 "Extrapolation Method",
396 createStandardIndiSelectionSw( m_indiP_extrapNoiseEstimateDomain,
397 "extrap_noiseEstimateDomain",
402 "Noise Estimate Domain",
405 createStandardIndiSelectionSw( m_indiP_extrapNoiseEstimateRange,
406 "extrap_noiseEstimateRange",
411 "Noise Estimate Range",
414 createStandardIndiSelectionSw( m_indiP_extrapNoiseEstimateStatistic,
415 "extrap_noiseEstimateStatistic",
420 "Noise Estimate Statistic",
423 createStandardIndiSelectionSw(
424 m_indiP_extrapClosedLoopOlEstimateMethod,
425 "extrap_closedLoopOlEstimateMethod",
430 "Closed Loop OL Estimate Method",
433 createStandardIndiSelectionSw(
434 m_indiP_extrapPowerLawCrossoverMode,
435 "extrap_powerLawCrossoverMode",
440 "Power-Law Crossover Mode",
444 int handleExtrapMethodPropertyForTest(
const pcf::IndiProperty &
ipRecv )
449 int handleExtrapNoiseEstimateDomainPropertyForTest(
const pcf::IndiProperty &
ipRecv )
454 int handleExtrapNoiseEstimateRangePropertyForTest(
const pcf::IndiProperty &
ipRecv )
459 int handleExtrapNoiseEstimateStatisticPropertyForTest(
const pcf::IndiProperty &
ipRecv )
464 int handleExtrapClosedLoopOlEstimateMethodPropertyForTest(
const pcf::IndiProperty &
ipRecv )
469 int handleExtrapPowerLawCrossoverModePropertyForTest(
const pcf::IndiProperty &
ipRecv )
474 pcf::IndiElement::SwitchStateType extrapMethodElementStateForTest(
const std::string &element )
const
476 return m_indiP_extrapMethod[element].getSwitchState();
479 pcf::IndiElement::SwitchStateType extrapNoiseEstimateDomainElementStateForTest(
const std::string &element )
const
481 return m_indiP_extrapNoiseEstimateDomain[element].getSwitchState();
484 pcf::IndiElement::SwitchStateType extrapNoiseEstimateRangeElementStateForTest(
const std::string &element )
const
486 return m_indiP_extrapNoiseEstimateRange[element].getSwitchState();
489 pcf::IndiElement::SwitchStateType
490 extrapNoiseEstimateStatisticElementStateForTest(
const std::string &element )
const
492 return m_indiP_extrapNoiseEstimateStatistic[element].getSwitchState();
495 pcf::IndiElement::SwitchStateType
496 extrapClosedLoopOlEstimateMethodElementStateForTest(
const std::string &element )
const
498 return m_indiP_extrapClosedLoopOlEstimateMethod[element].getSwitchState();
501 pcf::IndiElement::SwitchStateType extrapPowerLawCrossoverModeElementStateForTest(
const std::string &element )
const
503 return m_indiP_extrapPowerLawCrossoverMode[element].getSwitchState();
513TEST_CASE(
"modalGainOpt placeholder harness instantiates the app",
"[modalGainOpt]" )
516 #ifdef MODALGAINOPT_TEST_DOXYGEN_REF
521 SECTION(
"default construction succeeds" )
528 SECTION(
"OL process-method helpers map consistently" )
593TEST_CASE(
"modalGainOpt configuration loads PSD-processing settings without "
594 "toggling autoUpdate",
597 modalGainOptHarness app;
601 mx::app::writeConfigFile(
"/tmp/modalGainOpt_test.conf",
602 {
"loop",
"loop",
"loop",
"loop",
"loop",
603 "loop",
"extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
604 "extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
605 "extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
606 "extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
607 "extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
608 "extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
"extrapolation",
609 "extrapolation",
"extrapolation" },
618 "noiseEstimateDomain",
619 "noiseEstimateRange",
620 "noiseEstimateStatistic",
621 "noiseEstimateLowFreqMaxHz",
622 "closedLoopOlEstimateMethod",
626 "powerLawMatchFallbackWindowHz",
627 "powerLawCrossoverMode",
628 "powerLawAutoSmoothWidthHz",
629 "powerLawAutoMaxFreqFraction",
631 "powerLawOnlyAboveFreq",
632 "powerLawFitIncludesMatchPoint",
633 "powerLawFitMinFreqHz",
634 "powerLawFitMaxFreqHz",
635 "powerLawFitBinWidthHz",
639 "peakDetectBroadFactor",
640 "peakDetectMinWidthLog",
646 "clSignificanceThreshold",
647 "clMinSignificantFraction",
656 "closed_loop_pre_xfer",
665 "auto_smoothed_crossing",
686 app.readConfigFile(
"/tmp/modalGainOpt_test.conf" );
690 #ifdef MODALGAINOPT_TEST_DOXYGEN_REF
696 REQUIRE( app.shutdownState() == 0 );
697 REQUIRE( app.autoUpdate() ==
false );
698 REQUIRE( app.gainGain() == Approx( 0.35F ) );
699 REQUIRE( app.gainLeak() == Approx( 0.65F ) );
705 REQUIRE( app.extrapConfig().m_noiseEstimateDomain ==
"closed-loop-pre-xfer" );
706 REQUIRE( app.extrapConfig().m_noiseEstimateRange ==
"low-freq" );
707 REQUIRE( app.extrapConfig().m_noiseEstimateStatistic ==
"minimum" );
708 REQUIRE( app.extrapConfig().m_noiseEstimateLowFreqMaxHz == Approx( 123.0F ) );
709 REQUIRE( app.extrapConfig().m_closedLoopOlEstimateMethod ==
"ntf-aware" );
710 REQUIRE( app.extrapConfig().m_powerLawIndex == Approx( 1.5F ) );
711 REQUIRE( app.extrapConfig().m_powerLawNormFreq == Approx( 15.0F ) );
712 REQUIRE( app.extrapConfig().m_powerLawMatchFreq == Approx( 12.5F ) );
713 REQUIRE( app.extrapConfig().m_powerLawMatchFallbackWindowHz == Approx( 7.5F ) );
714 REQUIRE( app.extrapConfig().m_powerLawCrossoverMode ==
"auto-smoothed-crossing" );
715 REQUIRE( app.extrapConfig().m_powerLawAutoSmoothWidthHz == Approx( 37.5F ) );
716 REQUIRE( app.extrapConfig().m_powerLawAutoMaxFreqFraction == Approx( 0.4F ) );
717 REQUIRE( app.extrapConfig().m_fitPowerLawIndex ==
true );
718 REQUIRE( app.extrapConfig().m_powerLawOnlyAboveFreq == Approx( 250.0F ) );
719 REQUIRE( app.extrapConfig().m_powerLawFitIncludesMatchPoint ==
false );
720 REQUIRE( app.extrapConfig().m_powerLawFitMinFreqHz == Approx( 100.0F ) );
721 REQUIRE( app.extrapConfig().m_powerLawFitMaxFreqHz == Approx( 900.0F ) );
722 REQUIRE( app.extrapConfig().m_powerLawFitBinWidthHz == Approx( 80.0F ) );
723 REQUIRE( app.extrapConfig().m_powerLawBlendBins == 6 );
724 REQUIRE( app.extrapConfig().m_peakDetectWidthHz == Approx( 55.0F ) );
725 REQUIRE( app.extrapConfig().m_peakDetectFactor == Approx( 4.0F ) );
726 REQUIRE( app.extrapConfig().m_peakDetectBroadFactor == Approx( 2.5F ) );
727 REQUIRE( app.extrapConfig().m_peakDetectMinWidthLog == Approx( 0.03F ) );
728 REQUIRE( app.extrapConfig().m_peakDetectPasses == 3 );
729 REQUIRE( app.extrapConfig().m_peakMoffatBeta == Approx( 8.0F ) );
730 REQUIRE( app.extrapConfig().m_dropoutGapFactor == Approx( 0.12F ) );
731 REQUIRE( app.extrapConfig().m_dropoutTinyFactor == Approx( 1.0e-6F ) );
732 REQUIRE( app.extrapConfig().m_dropoutMaxBins == 6 );
733 REQUIRE( app.extrapConfig().m_clSignificanceThreshold == Approx( 1.25F ) );
734 REQUIRE( app.extrapConfig().m_clMinSignificantFraction == Approx( 0.07F ) );
737TEST_CASE(
"modalGainOpt restores current selection when extrapolation switches "
738 "receive all-off updates",
741 modalGainOptHarness app;
742 app.initExtrapSelectionPropertiesForTest();
744 SECTION(
"extrapolation method is restored" )
748 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
754 REQUIRE( app.handleExtrapMethodPropertyForTest( ip ) == 0 );
757 pcf::IndiElement::On );
759 pcf::IndiElement::Off );
762 SECTION(
"noise-estimate domain is restored" )
766 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
768 pcf::IndiElement::Off ) );
770 pcf::IndiElement::Off ) );
772 REQUIRE( app.handleExtrapNoiseEstimateDomainPropertyForTest( ip ) == 0 );
774 REQUIRE( app.extrapConfig().m_noiseEstimateDomain ==
"closed-loop-pre-xfer" );
777 REQUIRE( app.extrapNoiseEstimateDomainElementStateForTest(
781 SECTION(
"noise-estimate range is restored" )
785 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
787 pcf::IndiElement::Off ) );
789 pcf::IndiElement::Off ) );
791 REQUIRE( app.handleExtrapNoiseEstimateRangePropertyForTest( ip ) == 0 );
793 REQUIRE( app.extrapConfig().m_noiseEstimateRange ==
"low-freq" );
794 REQUIRE( app.extrapNoiseEstimateRangeElementStateForTest(
796 REQUIRE( app.extrapNoiseEstimateRangeElementStateForTest(
800 SECTION(
"noise-estimate statistic is restored" )
804 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
806 pcf::IndiElement::Off ) );
808 pcf::IndiElement::Off ) );
810 REQUIRE( app.handleExtrapNoiseEstimateStatisticPropertyForTest( ip ) == 0 );
812 REQUIRE( app.extrapConfig().m_noiseEstimateStatistic ==
"minimum" );
813 REQUIRE( app.extrapNoiseEstimateStatisticElementStateForTest(
819 SECTION(
"closed-loop OL estimate method is restored" )
823 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
825 pcf::IndiElement::Off ) );
827 pcf::IndiElement::Off ) );
829 REQUIRE( app.handleExtrapClosedLoopOlEstimateMethodPropertyForTest( ip ) == 0 );
831 REQUIRE( app.extrapConfig().m_closedLoopOlEstimateMethod ==
"ntf-aware" );
838 SECTION(
"power-law crossover mode is restored" )
842 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
844 pcf::IndiElement::Off ) );
846 pcf::IndiElement::Off ) );
848 REQUIRE( app.handleExtrapPowerLawCrossoverModePropertyForTest( ip ) == 0 );
850 REQUIRE( app.extrapConfig().m_powerLawCrossoverMode ==
"auto-smoothed-crossing" );
853 REQUIRE( app.extrapPowerLawCrossoverModeElementStateForTest(
858TEST_CASE(
"modalPsdProcessor falls back when the requested power-law fit has "
859 "too little frequency span",
872 std::vector<float> measuredPsd{ 1.0e-6F, 9.0e-7F, 8.0e-7F, 7.0e-7F, 6.0e-7F };
873 std::vector<float> freq{ 0.0F, 25.0F, 50.0F, 75.0F, 100.0F };
884TEST_CASE(
"modalPsdProcessor can estimate noise in closed-loop space before OL "
892 std::vector<float> measuredPsd{ 0.0F, 5.0F, 5.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F };
893 std::vector<float> freq{ 0.0F, 1.0F, 2.0F, 3.0F, 4.0F, 5.0F, 6.0F, 7.0F };
894 std::vector<float> correctionPsd( measuredPsd.size(), 0.5F );
895 correctionPsd[0] = 1.0F;
904 REQUIRE( result.
m_noisePsd[1] == Approx( 1.0F ) );
908TEST_CASE(
"modalPsdProcessor can estimate noise from the low-frequency end",
"[modalGainOpt]" )
910 std::vector<float> measuredPsd{ 0.0F, 2.0F, 2.0F, 20.0F, 20.0F, 20.0F, 20.0F, 20.0F };
911 std::vector<float> freq{ 0.0F, 1.0F, 2.0F, 3.0F, 4.0F, 5.0F, 6.0F, 7.0F };
912 std::vector<float> noisePsd;
913 float noiseFloor = 0.0F;
919 REQUIRE( noiseFloor == Approx( 2.0F ) );
920 REQUIRE( noisePsd[1] == Approx( 2.0F ) );
923TEST_CASE(
"modalPsdProcessor can estimate noise using the minimum PSD in range",
"[modalGainOpt]" )
925 std::vector<float> measuredPsd{ 0.0F, 8.0F, 4.0F, 2.0F, 20.0F, 20.0F, 20.0F, 20.0F };
926 std::vector<float> freq{ 0.0F, 1.0F, 2.0F, 3.0F, 4.0F, 5.0F, 6.0F, 7.0F };
927 std::vector<float> noisePsd;
928 float noiseFloor = 0.0F;
934 REQUIRE( noiseFloor == Approx( 2.0F ) );
935 REQUIRE( noisePsd[1] == Approx( 2.0F ) );
938TEST_CASE(
"modalPsdProcessor can limit low-frequency noise estimation to a max "
942 std::vector<float> measuredPsd{ 0.0F, 20.0F, 20.0F, 2.0F, 2.0F, 20.0F, 20.0F, 20.0F, 20.0F, 20.0F };
943 std::vector<float> freq{ 0.0F, 1.0F, 2.0F, 3.0F, 4.0F, 5.0F, 6.0F, 7.0F, 8.0F, 9.0F };
944 std::vector<float> noisePsd;
945 float noiseFloor = 0.0F;
957 REQUIRE( noiseFloor == Approx( 20.0F ) );
958 REQUIRE( noisePsd[1] == Approx( 20.0F ) );
961TEST_CASE(
"modalPsdProcessor can reconstruct OL PSD with NTF-aware closed-loop "
970 std::vector<float> measuredPsd{ 0.0F, 10.0F, 10.0F, 2.0F, 2.0F, 2.0F, 2.0F, 2.0F };
971 std::vector<float> freq{ 0.0F, 1.0F, 2.0F, 3.0F, 4.0F, 5.0F, 6.0F, 7.0F };
972 std::vector<float> etfPsd( measuredPsd.size(), 0.5F );
973 std::vector<float> ntfPsd( measuredPsd.size(), 2.0F );
985 REQUIRE( result.
m_noisePsd[1] == Approx( 2.0F ) );
989TEST_CASE(
"modalPsdProcessor fits closed-loop noise on raw CL PSD even when OL "
990 "reconstruction is NTF-aware",
1001 std::vector<float> measuredPsd{ 0.0F, 20.0F, 18.0F, 2.0F, 2.0F, 2.0F, 2.0F, 2.0F };
1002 std::vector<float> freq{ 0.0F, 1.0F, 2.0F, 3.0F, 4.0F, 5.0F, 6.0F, 7.0F };
1003 std::vector<float> etfPsd( measuredPsd.size(), 0.5F );
1004 std::vector<float> ntfPsd( measuredPsd.size(), 4.0F );
1014 REQUIRE( result.
m_noisePsd[1] == Approx( 2.0F ) );
1018TEST_CASE(
"modalPsdProcessor power-law-only matches moffat handoff when forced "
1019 "to pure power law above a cutoff",
1022 std::vector<float> measuredPsd{ 0.0F, 11.0F, 9.0F, 7.0F, 5.0F, 4.0F, 3.4F, 3.0F, 2.8F, 2.6F, 2.4F };
1023 std::vector<float> freq{ 0.0F, 20.0F, 40.0F, 60.0F, 80.0F, 100.0F, 120.0F, 140.0F, 160.0F, 180.0F, 200.0F };
1026 powerCfg.
m_method =
"power-law-only";
1033 moffatCfg.
m_method =
"moffat-peaks";
1045 REQUIRE( moffatResult.
m_peaks.empty() );
1047 for(
size_t n = 0; n < powerResult.
m_processPsd.size(); ++n )
1053TEST_CASE(
"modalPsdProcessor power-law-only repairs deep dropouts below the "
1054 "pure-power-law cutoff",
1057 std::vector<float> measuredPsd{ 0.0F, 11.0F, 10.0F, 2.5F, 9.0F, 8.0F, 7.5F, 7.0F };
1058 std::vector<float> freq{ 0.0F, 20.0F, 40.0F, 60.0F, 80.0F, 100.0F, 120.0F, 140.0F };
1079TEST_CASE(
"modalPsdProcessor can auto-select the power-law crossover from a "
1080 "smoothed noise crossing",
1083 std::vector<float> measuredPsd{ 0.0F, 10.0F, 8.5F, 6.5F, 4.5F, 3.1F, 2.7F, 2.55F, 4.6F, 2.51F, 2.5F };
1084 std::vector<float> freq{ 0.0F, 20.0F, 40.0F, 60.0F, 80.0F, 100.0F, 120.0F, 140.0F, 160.0F, 180.0F, 200.0F };
1108TEST_CASE(
"modalPsdProcessor falls back to the highest-frequency smoothed "
1109 "minimum when no noise crossing exists",
1112 std::vector<float> smoothedProcessPsd{ 5.0F, 4.0F, 2.0F, 1.5F, 1.5F };
1113 std::vector<float> noisePsd{ 1.0F, 1.0F, 1.0F, 1.0F, 1.0F };
1114 std::vector<float> freq{ 0.0F, 25.0F, 50.0F, 75.0F, 100.0F };
1116 float crossoverFreq = 0.0F;
1124 REQUIRE( crossoverFreq == Approx( 100.0F ) );
1127TEST_CASE(
"modalPsdProcessor treats a below-to-above sign change as a valid "
1128 "smoothed noise crossing",
1131 std::vector<float> smoothedProcessPsd{ 0.5F, 0.8F, 1.0F, 1.2F, 1.4F };
1132 std::vector<float> noisePsd{ 1.0F, 1.0F, 1.0F, 1.0F, 1.0F };
1133 std::vector<float> freq{ 0.0F, 25.0F, 50.0F, 75.0F, 100.0F };
1135 float crossoverFreq = 0.0F;
1143 REQUIRE( crossoverFreq == Approx( 50.0F ) );
1146TEST_CASE(
"modalPsdProcessor auto crossover can cap the search to a fraction "
1147 "of the sampled maximum frequency",
1150 std::vector<float> smoothedProcessPsd{ 5.0F, 4.0F, 1.5F, 0.8F, 0.7F, 1.4F, 1.6F };
1151 std::vector<float> noisePsd{ 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F };
1152 std::vector<float> freq{ 0.0F, 100.0F, 200.0F, 300.0F, 400.0F, 900.0F, 1000.0F };
1154 float crossoverFreq = 0.0F;
1162 REQUIRE( crossoverFreq == Approx( 271.42856F ) );
1165TEST_CASE(
"modalPsdProcessor snaps the effective auto crossover to the next "
1166 "sampled frequency bin",
1169 std::vector<float> rawProcessPsd{ 5.0F, 4.0F, 2.0F, 0.8F, 0.7F };
1170 std::vector<float> smoothedProcessPsd{ 5.0F, 4.0F, 2.0F, 0.8F, 0.7F };
1171 std::vector<float> noisePsd{ 1.0F, 1.0F, 1.0F, 1.0F, 1.0F };
1172 std::vector<float> freq{ 0.0F, 100.0F, 200.0F, 300.0F, 400.0F };
1174 float matchFreq = 0.0F;
1175 float onlyAboveFreq = 0.0F;
1182 "auto-smoothed-crossing",
1186 REQUIRE( matchFreq == Approx( 300.0F ) );
1187 REQUIRE( onlyAboveFreq == Approx( 300.0F ) );
1190TEST_CASE(
"modalPsdProcessor anchors the power-law match to the smoothed "
1194 std::vector<float> rawProcessPsd{ 1.0F, 1.0F, 100.0F, 0.5F, 0.25F };
1195 std::vector<float> smoothedProcessPsd{ 1.0F, 1.0F, 10.0F, 0.5F, 0.25F };
1196 std::vector<float> noisePsd{ 0.1F, 0.1F, 0.1F, 0.1F, 0.1F };
1197 std::vector<float> freq{ 0.0F, 10.0F, 20.0F, 30.0F, 40.0F };
1199 std::vector<float> continuumPsd;
1200 float extrapolation = 0.0F;
1201 size_t anchorIndex = 0;
1215 REQUIRE( anchorIndex >= 1 );
1216 REQUIRE( extrapolation == Approx( 20.0F ) );
1219TEST_CASE(
"modalPsdProcessor power-law-only auto handoff matches the "
1220 "smoothed crossover exactly without a blend ramp",
1223 std::vector<float> measuredPsd{ 1.0F, 1.2F, 41.0F, 31.0F, 3.0F, 2.0F };
1224 std::vector<float> smoothedProcessPsd{ 1.0F, 45.0F, 35.0F, 25.0F, 2.0F, 1.5F };
1225 std::vector<float> noisePsd{ 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F };
1226 std::vector<float> freq{ 0.0F, 10.0F, 20.0F, 30.0F, 40.0F, 50.0F };
1238 std::vector<float> processPsd;
1239 float extrapolation = 0.0F;
1240 size_t anchorIndex = 0;
1241 std::vector<unsigned char> repairMask;
1242 float usedPowerLawIndex = 0.0F;
1243 size_t fitBinsUsed = 0;
1258 REQUIRE( usedPowerLawIndex == Approx( 1.0F ) );
1259 REQUIRE( processPsd[1] == Approx( measuredPsd[1] - noisePsd[1] ) );
1260 REQUIRE( processPsd[3] == Approx( measuredPsd[3] - noisePsd[3] ) );
1261 REQUIRE( processPsd[4] == Approx( smoothedProcessPsd[4] ) );
1262 REQUIRE( processPsd[5] == Approx( 1.6F ) );
1265TEST_CASE(
"modalPsdProcessor repairs raw disturbance dropouts before "
1269 std::vector<float> measuredPsd{ 0.0F, 11.0F, 10.0F, 1.2F, 9.0F, 8.0F, 1.0F };
1270 std::vector<float> freq{ 0.0F, 10.0F, 20.0F, 30.0F, 40.0F, 50.0F, 60.0F };
1288TEST_CASE(
"modalPsdProcessor repairs trailing high-frequency dropout runs",
"[modalGainOpt]" )
1290 std::vector<float> processPsd{ 10.0F, 9.0F, 8.0F, 1.0e-8F, 1.0e-8F, 1.0e-8F };
1291 std::vector<float> freq{ 0.0F, 10.0F, 20.0F, 30.0F, 40.0F, 50.0F };
1297 REQUIRE( processPsd[3] > 1.0F );
1298 REQUIRE( processPsd[4] > 1.0F );
1299 REQUIRE( processPsd[5] > 1.0F );
1300 REQUIRE( processPsd[3] == Approx( 16.0F / 3.0F ) );
1301 REQUIRE( processPsd[4] == Approx( 4.0F ) );
1302 REQUIRE( processPsd[5] == Approx( 3.2F ) );
1305TEST_CASE(
"modalPsdProcessor keeps trailing dropout repair bounded when the "
1306 "last good bins rise",
1309 std::vector<float> processPsd{ 1.0F, 2.0F, 4.0F, 1.0e-8F, 1.0e-8F, 1.0e-8F };
1310 std::vector<float> freq{ 0.0F, 10.0F, 20.0F, 30.0F, 40.0F, 50.0F };
1316 REQUIRE( processPsd[3] == Approx( 8.0F / 3.0F ) );
1317 REQUIRE( processPsd[4] == Approx( 2.0F ) );
1318 REQUIRE( processPsd[5] == Approx( 1.6F ) );
1321TEST_CASE(
"modalPsdProcessor does not treat a sharp post-peak decline as a "
1322 "dropout unless it is truly tiny",
1325 std::vector<float> processPsd{ 1.0e-4F, 1.0e-6F, 1.0e-8F, 1.0e-8F, 1.0e-8F };
1326 std::vector<float> freq{ 0.0F, 10.0F, 20.0F, 30.0F, 40.0F };
1332 REQUIRE( processPsd[2] == Approx( 1.0e-8F ) );
1333 REQUIRE( processPsd[3] == Approx( 1.0e-8F ) );
1334 REQUIRE( processPsd[4] == Approx( 1.0e-8F ) );
1342TEST_CASE(
"modalGainOpt published gain arrays keep LP and max LP outputs distinct",
"[modalGainOpt]" )
1344 modalGainOptHarness app;
1346 app.configurePublishedGainState( { 2.0F, 4.0F },
1358 std::vector<float> currentData( 2, -1.0F );
1359 std::vector<float> siRawData( 2, -1.0F );
1360 std::vector<float> siData( 2, -1.0F );
1361 std::vector<float> maxSiData( 2, -1.0F );
1362 std::vector<float> lpData( 2, -1.0F );
1363 std::vector<float> maxLpData( 2, -1.0F );
1364 std::vector<float> modeVarData( 6, -1.0F );
1366 app.writePublishedGainArraysForTest( currentData.data(),
1372 modeVarData.data() );
1374 mx::improc::eigenMap<float> modeVars( modeVarData.data(), 3, 2 );
1376 REQUIRE( currentData[0] == Approx( 3.0F ) );
1377 REQUIRE( currentData[1] == Approx( 5.0F ) );
1378 REQUIRE( siRawData[0] == Approx( 29.0F ) );
1379 REQUIRE( siRawData[1] == Approx( 31.0F ) );
1380 REQUIRE( siData[0] == Approx( 3.0F ) );
1381 REQUIRE( siData[1] == Approx( 5.0F ) );
1382 REQUIRE( maxSiData[0] == Approx( 7.0F ) );
1383 REQUIRE( maxSiData[1] == Approx( 11.0F ) );
1384 REQUIRE( lpData[0] == Approx( 13.0F ) );
1385 REQUIRE( lpData[1] == Approx( 17.0F ) );
1386 REQUIRE( maxLpData[0] == Approx( 19.0F ) );
1387 REQUIRE( maxLpData[1] == Approx( 23.0F ) );
1388 REQUIRE( modeVars( 0, 0 ) == Approx( 0.1F ) );
1389 REQUIRE( modeVars( 1, 0 ) == Approx( 1.1F ) );
1390 REQUIRE( modeVars( 2, 0 ) == Approx( 2.1F ) );
1391 REQUIRE( modeVars( 0, 1 ) == Approx( 0.2F ) );
1392 REQUIRE( modeVars( 1, 1 ) == Approx( 1.2F ) );
1393 REQUIRE( modeVars( 2, 1 ) == Approx( 2.2F ) );
1401TEST_CASE(
"modalGainOpt published gain arrays apply calibration scaling",
"[modalGainOpt]" )
1403 modalGainOptHarness app;
1405 app.configurePublishedGainState( { 6.0F, 3.0F },
1417 std::vector<float> currentData( 2, -1.0F );
1418 std::vector<float> siRawData( 2, -1.0F );
1419 std::vector<float> siData( 2, -1.0F );
1420 std::vector<float> maxSiData( 2, -1.0F );
1421 std::vector<float> lpData( 2, -1.0F );
1422 std::vector<float> maxLpData( 2, -1.0F );
1423 std::vector<float> modeVarData( 6, -1.0F );
1425 app.writePublishedGainArraysForTest( currentData.data(),
1431 modeVarData.data() );
1433 REQUIRE( currentData[0] == Approx( 2.0F ) );
1434 REQUIRE( currentData[1] == Approx( 1.5F ) );
1435 REQUIRE( siRawData[0] == Approx( 1.0F ) );
1436 REQUIRE( siRawData[1] == Approx( 1.25F ) );
1437 REQUIRE( siData[0] == Approx( 2.0F ) );
1438 REQUIRE( siData[1] == Approx( 1.5F ) );
1439 REQUIRE( maxSiData[0] == Approx( 4.0F ) );
1440 REQUIRE( maxSiData[1] == Approx( 2.25F ) );
1441 REQUIRE( lpData[0] == Approx( 5.0F ) );
1442 REQUIRE( lpData[1] == Approx( 2.5F ) );
1443 REQUIRE( maxLpData[0] == Approx( 7.0F ) );
1444 REQUIRE( maxLpData[1] == Approx( 3.0F ) );
1447TEST_CASE(
"modalGainOpt zero_gains request resets the integrated SI gains",
"[modalGainOpt]" )
1449 modalGainOptHarness app;
1451 app.configurePublishedGainState( { 2.0F, 4.0F },
1462 app.initZeroGainsPropertyForTest();
1464 REQUIRE( app.integratedSiGainsForTest()[0] == Approx( 3.0F ) );
1465 REQUIRE( app.integratedSiGainsForTest()[1] == Approx( 5.0F ) );
1467 REQUIRE( app.requestZeroGainsForTest() == 0 );
1468 REQUIRE( app.integratedSiGainsForTest()[0] == Approx( 0.0F ) );
1469 REQUIRE( app.integratedSiGainsForTest()[1] == Approx( 0.0F ) );
1472TEST_CASE(
"modalGainOpt SI gain integrator updates toward the raw optimum by delta",
"[modalGainOpt]" )
1474 modalGainOptHarness app;
1476 app.configureSiIntegratorStateForTest( { 10.0F, 3.0F }, { 4.0F, 1.0F }, 0.2F, 0.9F );
1478 app.updateIntegratedSiGainForTest( 0 );
1479 app.updateIntegratedSiGainForTest( 1 );
1481 REQUIRE( app.integratedSiGainsForTest()[0] == Approx( 4.8F ) );
1482 REQUIRE( app.integratedSiGainsForTest()[1] == Approx( 1.3F ) );
1489TEST_CASE(
"modalGainOpt counts enabled gain-factor modes using positive entries",
"[modalGainOpt]" )
1491 modalGainOptHarness app;
1494 #ifdef MODALGAINOPT_TEST_DOXYGEN_REF
1499 REQUIRE( app.countEnabledGainFactorsForTest( {} ) == 0 );
1500 REQUIRE( app.countEnabledGainFactorsForTest( { -0.2F, 0.0F, 0.1F, 2.0F, -3.0F } ) == 2 );
1501 REQUIRE( app.countEnabledGainFactorsForTest( { 1.0F, 0.5F, 0.25F } ) == 3 );
1508TEST_CASE(
"modalGainOpt gain-factor updates leave state unchanged when the "
1509 "frame is identical",
1512 modalGainOptHarness app;
1514 std::vector<float> storedGainFacts( { 1.0F, 0.5F, 0.0F } );
1516 app.setLoopForTest(
true );
1517 app.setPcOnForTest(
false );
1518 app.setModesOnForTest( 7 );
1519 app.setSinceChangeForTest( 12 );
1522 #ifdef MODALGAINOPT_TEST_DOXYGEN_REF
1527 REQUIRE( app.applyGainFactorUpdateForTest( storedGainFacts, { 1.0F, 0.5F, 0.0F },
false ) ==
false );
1528 REQUIRE( storedGainFacts == std::vector<float>( { 1.0F, 0.5F, 0.0F } ) );
1529 REQUIRE( app.modesOn() == 7 );
1530 REQUIRE( app.sinceChange() == 12 );
1538TEST_CASE(
"modalGainOpt gain-factor updates resize SI state and reset "
1539 "sinceChange on change",
1542 modalGainOptHarness app;
1544 std::vector<float> storedGainFacts( { 9.0F } );
1546 app.setLoopForTest(
true );
1547 app.setPcOnForTest(
false );
1548 app.setModesOnForTest( 0 );
1549 app.setSinceChangeForTest( 5 );
1551 REQUIRE( app.applyGainFactorUpdateForTest( storedGainFacts, { 0.0F, 1.5F, -2.0F, 3.0F },
false ) ==
true );
1552 REQUIRE( storedGainFacts == std::vector<float>( { 0.0F, 1.5F, -2.0F, 3.0F } ) );
1553 REQUIRE( app.modesOn() == 2 );
1554 REQUIRE( app.sinceChange() == -1 );
1561TEST_CASE(
"modalGainOpt multiplier updates leave state unchanged when the "
1562 "frame is identical",
1565 modalGainOptHarness app;
1567 std::vector<float> storedMultFacts( { 0.25F, 0.5F, 0.75F } );
1569 app.setLoopForTest(
true );
1570 app.setSinceChangeForTest( 14 );
1571 app.setGoptUpdatedForTest(
false );
1572 app.setPcgoptUpdatedForTest(
false );
1575 #ifdef MODALGAINOPT_TEST_DOXYGEN_REF
1580 REQUIRE( app.applyMultiplierUpdateForTest( storedMultFacts, { 0.25F, 0.5F, 0.75F },
false ) ==
false );
1581 REQUIRE( storedMultFacts == std::vector<float>( { 0.25F, 0.5F, 0.75F } ) );
1582 REQUIRE( app.sinceChange() == 14 );
1583 REQUIRE( app.goptUpdated() ==
false );
1584 REQUIRE( app.pcgoptUpdated() ==
false );
1592TEST_CASE(
"modalGainOpt SI multiplier updates set goptUpdated and reset sinceChange",
"[modalGainOpt]" )
1594 modalGainOptHarness app;
1596 std::vector<float> storedMultFacts( { 1.0F } );
1598 app.setLoopForTest(
true );
1599 app.setSinceChangeForTest( 6 );
1600 app.setGoptUpdatedForTest(
false );
1601 app.setPcgoptUpdatedForTest(
false );
1603 REQUIRE( app.applyMultiplierUpdateForTest( storedMultFacts, { 1.5F, 2.5F },
false ) ==
true );
1604 REQUIRE( storedMultFacts == std::vector<float>( { 1.5F, 2.5F } ) );
1605 REQUIRE( app.sinceChange() == -1 );
1606 REQUIRE( app.goptUpdated() ==
true );
1607 REQUIRE( app.pcgoptUpdated() ==
false );
1614TEST_CASE(
"modalGainOpt frequency updates leave state unchanged when the frame "
1618 modalGainOptHarness app;
1620 app.setFreqForTest( { 10.0F, 20.0F, 30.0F } );
1621 app.setFpsForTest( 60.0F );
1622 app.setSinceChangeForTest( 8 );
1623 app.setGoptUpdatedForTest(
false );
1624 app.setFreqUpdatedForTest(
false );
1627 #ifdef MODALGAINOPT_TEST_DOXYGEN_REF
1632 REQUIRE( app.applyFrequencyUpdateForTest( { 10.0F, 20.0F, 30.0F } ) ==
false );
1633 REQUIRE( app.freq() == std::vector<float>( { 10.0F, 20.0F, 30.0F } ) );
1634 REQUIRE( app.fps() == Approx( 60.0F ) );
1635 REQUIRE( app.sinceChange() == 8 );
1636 REQUIRE( app.goptUpdated() ==
false );
1637 REQUIRE( app.freqUpdated() ==
false );
1645TEST_CASE(
"modalGainOpt frequency updates resize state and refresh derived timing",
"[modalGainOpt]" )
1647 modalGainOptHarness app;
1649 app.setFreqForTest( { 5.0F } );
1650 app.setFpsForTest( 10.0F );
1651 app.setSinceChangeForTest( 4 );
1652 app.setGoptUpdatedForTest(
false );
1653 app.setFreqUpdatedForTest(
false );
1655 REQUIRE( app.applyFrequencyUpdateForTest( { 12.5F, 25.0F, 40.0F } ) ==
true );
1656 REQUIRE( app.freq() == std::vector<float>( { 12.5F, 25.0F, 40.0F } ) );
1657 REQUIRE( app.fps() == Approx( 80.0F ) );
1658 REQUIRE( app.sinceChange() == -1 );
1659 REQUIRE( app.goptUpdated() ==
true );
1660 REQUIRE( app.freqUpdated() ==
true );
1668TEST_CASE(
"modalGainOpt refreshes pending gopt structures without a PSD wakeup",
"[modalGainOpt]" )
1670 modalGainOptHarness app;
1673 #ifdef MODALGAINOPT_TEST_DOXYGEN_REF
1678 app.setFpsForTest( 1000.0F );
1679 app.setFreqForTest( { 100.0F, 200.0F, 300.0F } );
1680 app.configureGoptStructureInputsForTest( { 1.0F, 2.0F }, { 0.001F, 0.002F }, { 0.5F, 0.75F }, app.freq() );
1681 app.setFreqUpdatedForTest(
true );
1682 app.setGoptUpdatedForTest(
true );
1683 app.setPcgoptUpdatedForTest(
false );
1684 app.setPcOnForTest(
false );
1686 REQUIRE( app.goptCurrentSize() == 0 );
1687 REQUIRE( app.refreshGoptStructuresForTest() ==
true );
1688 REQUIRE( app.goptCurrentSize() == 2 );
1689 REQUIRE( app.goptUpdated() ==
false );
1690 REQUIRE( app.pcgoptUpdated() ==
false );
1691 REQUIRE( app.freqUpdated() ==
false );
1699TEST_CASE(
"modalGainOpt predictor publication preserves per-mode coefficient layout",
"[modalGainOpt]" )
1701 modalGainOptHarness app;
1703 app.configurePublishedPredictorState( { 2.0F, 4.0F },
1708 { { 0.1F, 0.2F }, { 0.4F } },
1709 { { 0.3F }, { 0.5F, 0.6F } },
1713 std::vector<float> pcGainData( 2, -1.0F );
1714 std::vector<float> aCoeffData( 8, -1.0F );
1715 std::vector<float> bCoeffData( 8, -1.0F );
1717 app.writePublishedPredictorArraysForTest( pcGainData.data(), aCoeffData.data(), 4, bCoeffData.data(), 4,
false );
1719 REQUIRE( pcGainData[0] == Approx( 3.0F ) );
1720 REQUIRE( pcGainData[1] == Approx( 5.0F ) );
1722 REQUIRE( aCoeffData[0] == Approx( 2.0F ) );
1723 REQUIRE( aCoeffData[1] == Approx( 0.1F ) );
1724 REQUIRE( aCoeffData[2] == Approx( 0.2F ) );
1725 REQUIRE( aCoeffData[3] == Approx( 0.0F ) );
1726 REQUIRE( aCoeffData[4] == Approx( 1.0F ) );
1727 REQUIRE( aCoeffData[5] == Approx( 0.4F ) );
1728 REQUIRE( aCoeffData[6] == Approx( 0.0F ) );
1729 REQUIRE( aCoeffData[7] == Approx( 0.0F ) );
1731 REQUIRE( bCoeffData[0] == Approx( 1.0F ) );
1732 REQUIRE( bCoeffData[1] == Approx( 0.3F ) );
1733 REQUIRE( bCoeffData[2] == Approx( 0.0F ) );
1734 REQUIRE( bCoeffData[3] == Approx( 0.0F ) );
1735 REQUIRE( bCoeffData[4] == Approx( 2.0F ) );
1736 REQUIRE( bCoeffData[5] == Approx( 0.5F ) );
1737 REQUIRE( bCoeffData[6] == Approx( 0.6F ) );
1738 REQUIRE( bCoeffData[7] == Approx( 0.0F ) );
1746TEST_CASE(
"modalGainOpt predictor publication blends existing values and "
1747 "clears stale coefficients",
1750 modalGainOptHarness app;
1752 app.configurePublishedPredictorState( { 2.0F, 4.0F },
1757 { { 0.1F, 0.5F }, { 0.9F } },
1758 { { 0.2F }, { 0.6F, 1.0F } },
1762 std::vector<float> pcGainData( { 1.0F, 9.0F } );
1763 std::vector<float> aCoeffData( { 9.0F, 1.0F, 2.0F, 3.0F, 8.0F, 4.0F, 5.0F, 6.0F } );
1764 std::vector<float> bCoeffData( { 7.0F, 1.0F, 2.0F, 3.0F, 6.0F, 4.0F, 5.0F, 6.0F } );
1766 app.writePublishedPredictorArraysForTest( pcGainData.data(), aCoeffData.data(), 4, bCoeffData.data(), 4,
true );
1768 REQUIRE( pcGainData[0] == Approx( 1.5F ) );
1769 REQUIRE( pcGainData[1] == Approx( 8.0F ) );
1771 REQUIRE( aCoeffData[0] == Approx( 2.0F ) );
1772 REQUIRE( aCoeffData[1] == Approx( 0.775F ) );
1773 REQUIRE( aCoeffData[2] == Approx( 1.625F ) );
1774 REQUIRE( aCoeffData[3] == Approx( 0.0F ) );
1775 REQUIRE( aCoeffData[4] == Approx( 1.0F ) );
1776 REQUIRE( aCoeffData[5] == Approx( 3.225F ) );
1777 REQUIRE( aCoeffData[6] == Approx( 0.0F ) );
1778 REQUIRE( aCoeffData[7] == Approx( 0.0F ) );
1780 REQUIRE( bCoeffData[0] == Approx( 1.0F ) );
1781 REQUIRE( bCoeffData[1] == Approx( 0.8F ) );
1782 REQUIRE( bCoeffData[2] == Approx( 0.0F ) );
1783 REQUIRE( bCoeffData[3] == Approx( 0.0F ) );
1784 REQUIRE( bCoeffData[4] == Approx( 2.0F ) );
1785 REQUIRE( bCoeffData[5] == Approx( 3.15F ) );
1786 REQUIRE( bCoeffData[6] == Approx( 4.0F ) );
1787 REQUIRE( bCoeffData[7] == Approx( 0.0F ) );
1795TEST_CASE(
"modalGainOpt SI mode counts update only while predictor control is off",
"[modalGainOpt]" )
1797 modalGainOptHarness app;
1800 #ifdef MODALGAINOPT_TEST_DOXYGEN_REF
1805 app.setPcOnForTest(
false );
1806 app.updateAppliedModeCountForTest( { 1.0F, 0.0F, -1.0F, 2.0F }, false );
1807 REQUIRE( app.modesOn() == 2 );
1809 app.setPcOnForTest(
true );
1810 app.updateAppliedModeCountForTest( { 5.0F, 4.0F, 3.0F }, false );
1811 REQUIRE( app.modesOn() == 2 );
1819TEST_CASE(
"modalGainOpt predictor publication clears stale coefficient blocks "
1820 "for zero-order modes",
1823 modalGainOptHarness app;
1825 app.configurePublishedPredictorState( { 8.0F, 2.0F },
1835 std::vector<float> pcGainData( { 9.0F, 10.0F } );
1836 std::vector<float> aCoeffData( { 7.0F, 1.0F, 2.0F, 3.0F, 6.0F, 4.0F, 5.0F, 6.0F } );
1837 std::vector<float> bCoeffData( { 5.0F, 7.0F, 8.0F, 9.0F, 4.0F, 10.0F, 11.0F, 12.0F } );
1839 app.writePublishedPredictorArraysForTest( pcGainData.data(), aCoeffData.data(), 4, bCoeffData.data(), 4,
false );
1841 REQUIRE( pcGainData[0] == Approx( 4.0F ) );
1842 REQUIRE( pcGainData[1] == Approx( 3.0F ) );
1844 REQUIRE( aCoeffData[0] == Approx( 0.0F ) );
1845 REQUIRE( aCoeffData[1] == Approx( 0.0F ) );
1846 REQUIRE( aCoeffData[2] == Approx( 0.0F ) );
1847 REQUIRE( aCoeffData[3] == Approx( 0.0F ) );
1848 REQUIRE( aCoeffData[4] == Approx( 0.0F ) );
1849 REQUIRE( aCoeffData[5] == Approx( 0.0F ) );
1850 REQUIRE( aCoeffData[6] == Approx( 0.0F ) );
1851 REQUIRE( aCoeffData[7] == Approx( 0.0F ) );
1853 REQUIRE( bCoeffData[0] == Approx( 0.0F ) );
1854 REQUIRE( bCoeffData[1] == Approx( 0.0F ) );
1855 REQUIRE( bCoeffData[2] == Approx( 0.0F ) );
1856 REQUIRE( bCoeffData[3] == Approx( 0.0F ) );
1857 REQUIRE( bCoeffData[4] == Approx( 0.0F ) );
1858 REQUIRE( bCoeffData[5] == Approx( 0.0F ) );
1859 REQUIRE( bCoeffData[6] == Approx( 0.0F ) );
1860 REQUIRE( bCoeffData[7] == Approx( 0.0F ) );
1868TEST_CASE(
"modalGainOpt PC mode counts update only while predictor control is on",
"[modalGainOpt]" )
1870 modalGainOptHarness app;
1872 app.setPcOnForTest(
false );
1873 app.updateAppliedModeCountForTest( { 1.0F, 2.0F, 3.0F }, true );
1874 REQUIRE( app.modesOn() == 0 );
1876 app.setPcOnForTest(
true );
1877 app.updateAppliedModeCountForTest( { -1.0F, 0.25F, 0.0F, 0.75F }, true );
1878 REQUIRE( app.modesOn() == 2 );
1886TEST_CASE(
"modalGainOpt PC gain-factor updates preserve applied mode counts "
1887 "while predictor control is off",
1890 modalGainOptHarness app;
1892 std::vector<float> storedPcGainFacts( { 0.5F, 0.5F } );
1894 app.setLoopForTest(
false );
1895 app.setPcOnForTest(
false );
1896 app.setModesOnForTest( 3 );
1897 app.setSinceChangeForTest( 9 );
1899 REQUIRE( app.applyGainFactorUpdateForTest( storedPcGainFacts, { 1.0F, 0.0F, 2.0F },
true ) ==
true );
1900 REQUIRE( storedPcGainFacts == std::vector<float>( { 1.0F, 0.0F, 2.0F } ) );
1901 REQUIRE( app.modesOn() == 3 );
1902 REQUIRE( app.sinceChange() == 9 );
1910TEST_CASE(
"modalGainOpt PC multiplier updates set pcgoptUpdated without "
1911 "touching SI optimizer flags",
1914 modalGainOptHarness app;
1916 std::vector<float> storedPcMultFacts( { 0.1F, 0.2F } );
1918 app.setLoopForTest(
false );
1919 app.setSinceChangeForTest( 11 );
1920 app.setGoptUpdatedForTest(
false );
1921 app.setPcgoptUpdatedForTest(
false );
1923 REQUIRE( app.applyMultiplierUpdateForTest( storedPcMultFacts, { 0.3F, 0.4F, 0.5F },
true ) ==
true );
1924 REQUIRE( storedPcMultFacts == std::vector<float>( { 0.3F, 0.4F, 0.5F } ) );
1925 REQUIRE( app.sinceChange() == 11 );
1926 REQUIRE( app.goptUpdated() ==
false );
1927 REQUIRE( app.pcgoptUpdated() ==
true );
The MagAO-X PSD-based gain optimizer.
int countEnabledGainFactors(const std::vector< float > &gainFacts) const
Count how many modes are enabled by a gain-factor vector.
void updateAppliedModeCount(const std::vector< float > &gainFacts, bool predictorPath)
bool applyMultiplierUpdate(std::vector< float > &multFacts, const float *incoming, uint32_t width, bool predictorPath)
bool refreshGoptStructures()
bool applyFrequencyUpdate(const float *incoming, size_t size)
Apply an incoming frequency frame to the stored frequency scale.
virtual void loadConfig()
virtual void setupConfig()
bool applyGainFactorUpdate(std::vector< float > &gainFacts, const float *incoming, uint32_t width, bool predictorPath)
Apply an incoming gain-factor frame to one of the stored gain vectors.
std::string m_noiseEstimateStatistic
std::string m_powerLawCrossoverMode
How the power-law match/cutoff frequencies were chosen.
realT m_noiseEstimateLowFreqMaxHz
std::string m_noiseEstimateDomain
The domain used to estimate the flat noise floor.
realT m_powerLawOnlyAboveFreq
Above this frequency, force the extrapolation to be power-law only.
realT m_powerLawMatchFallbackWindowHz
size_t m_powerLawFitBinsUsed
The number of populated median bins used in the exponent fit.
realT m_noiseFloor
The fitted flat noise floor.
std::vector< identifiedPeak1D > m_peaks
The peaks detected by the Moffat extrapolator.
static mx::error_t buildSmoothedProcessPsd(std::vector< realT > &smoothedProcessPsd, const std::vector< realT > &rawProcessPsd, const std::vector< realT > &freq, realT smoothWidthHz)
static mx::error_t findAutoPowerLawCrossoverFreq(realT &crossoverFreq, const std::vector< realT > &smoothedProcessPsd, const std::vector< realT > &noisePsd, const std::vector< realT > &freq, realT maxFreqFraction)
bool m_fitPowerLawIndex
Whether to fit the power-law exponent from high-frequency bins.
static mx::error_t estimatePowerLawContinuum(std::vector< realT > &continuumPsd, realT &extrapolation, size_t &anchorIndex, const std::vector< realT > &rawProcessPsd, const std::vector< realT > &anchorProcessPsd, const std::vector< realT > &noisePsd, const std::vector< realT > &freq, realT powerLawIndex, realT powerLawNormFreq, realT powerLawMatchFreq, realT powerLawMatchFallbackWindowHz, bool fitPowerLawIndex=false, realT powerLawFitMinFreqHz=c_defaultPowerLawFitMinFreqHz, realT powerLawFitMaxFreqHz=c_defaultPowerLawFitMaxFreqHz, realT powerLawFitBinWidthHz=c_defaultPowerLawFitBinWidthHz, bool powerLawFitIncludesMatchPoint=c_defaultPowerLawFitIncludesMatchPoint, realT *usedPowerLawIndex=nullptr, size_t *fitBinsUsed=nullptr)
std::vector< realT > m_noisePsd
The flat noise PSD estimate.
std::vector< realT > m_processPsd
The disturbance PSD used for optimization.
realT m_powerLawOnlyAboveFreq
Above this frequency, force the extrapolation to be power-law only.
realT m_extrapolation
The continuum normalization at m_powerLawNormFreq.
realT m_powerLawNormFreq
The normalization frequency for the power-law continuum.
std::string m_closedLoopOlEstimateMethod
Which CL-to-OL reconstruction method was used.
std::string m_noiseEstimateRange
Which end of the PSD is used to estimate the flat noise floor.
std::vector< realT > m_rawProcessPsd
The unsmoothed, unextrapolated OL disturbance PSD.
realT m_peakDetectBroadFactor
The lower factor used for broad-peak candidates.
realT m_powerLawAutoSmoothWidthHz
static mx::error_t estimateProcessPsdPowerLawOnly(std::vector< realT > &processPsd, realT &extrapolation, size_t &anchorIndex, std::vector< unsigned char > &repairMask, const std::vector< realT > &measuredPsd, const std::vector< realT > &anchorProcessPsd, const std::vector< realT > &noisePsd, const std::vector< realT > &freq, const processModelConfig &config, realT *usedPowerLawIndex=nullptr, size_t *fitBinsUsed=nullptr)
Build a disturbance PSD from only the extrapolated 1/f^a continuum.
realT m_powerLawIndex
The power-law exponent used in extrapolation.
realT m_powerLawFitMaxFreqHz
The high edge of the exponent-fit range.
static mx::error_t estimateNoisePsd(std::vector< realT > &noisePsd, realT &noiseFloor, const std::vector< realT > &measuredPsd, const std::vector< realT > &freq, size_t modeIndex, std::string noiseEstimateRange=c_defaultNoiseEstimateRange, std::string noiseEstimateStatistic=c_defaultNoiseEstimateStatistic, realT noiseEstimateLowFreqMaxHz=c_defaultNoiseEstimateLowFreqMaxHz)
Estimate the flat noise PSD using the configured modalGainOpt statistic.
realT m_powerLawMatchFreq
realT m_peakDetectFactor
The minimum factor above the smoothed PSD for a strong peak.
static mx::error_t fillProcessPsdDropouts(std::vector< realT > &processPsd, const std::vector< realT > &freq, const std::vector< unsigned char > &repairMask, realT gapFactor, realT tinyFactor, size_t maxGapBins, realT powerLawIndex)
Fill isolated or short dropout runs in a disturbance PSD.
static mx::error_t analyzePsd(processResults &result, const std::vector< realT > &measuredPsd, const std::vector< realT > &freq, size_t modeIndex, const processModelConfig &config, realT lpContinuumFreq=static_cast< realT >(0), realT lpContinuumWidthHz=c_defaultLpContinuumWidthHz, const std::vector< realT > *etfPsd=nullptr, const std::vector< realT > *ntfPsd=nullptr)
Build the noise PSD, disturbance PSD, and LP continuum PSD for one mode.
realT m_powerLawIndex
The power-law exponent in .
static mx::error_t resolvePowerLawCrossoverFrequencies(realT &powerLawMatchFreq, realT &powerLawOnlyAboveFreq, const std::vector< realT > &rawProcessPsd, const std::vector< realT > &smoothedProcessPsd, const std::vector< realT > &noisePsd, const std::vector< realT > &freq, std::string powerLawCrossoverMode, realT powerLawAutoMaxFreqFraction)
std::string m_powerLawCrossoverMode
How the power-law match/cutoff frequencies are chosen.
std::string m_noiseEstimateDomain
The domain used to estimate the flat noise floor.
bool m_powerLawIndexFitSucceeded
Whether the exponent fit succeeded and was applied.
realT m_powerLawAutoMaxFreqFraction
std::string m_closedLoopOlEstimateMethod
realT m_powerLawFitMinFreqHz
The low edge of the exponent-fit range.
realT m_powerLawMatchFreq
std::string m_method
The extrapolation method name.
realT m_powerLawFitBinWidthHz
The width of the exponent-fit median bins.
Configuration of the disturbance-PSD extrapolation model.
Results of modal PSD noise estimation and disturbance extrapolation.
TEST_CASE("modalGainOpt placeholder harness instantiates the app", "[modalGainOpt]")
int extrapClosedLoopOlEstimateMethodFromElement(const std::string &element)
std::string extrapClosedLoopOlEstimateMethodLabel(int method)
return handleExtrapNoiseEstimateStatisticProperty(ipRecv)
int olProcessMethodFromName(std::string method)
const pcf::IndiProperty & ipRecv
return handleExtrapNoiseEstimateRangeProperty(ipRecv)
return handleExtrapClosedLoopOlEstimateMethodProperty(ipRecv)
int olProcessMethodFromElement(const std::string &element)
std::string extrapNoiseEstimateDomainElement(int domain)
static constexpr int c_olProcessPowerLawOnly
std::string extrapNoiseEstimateRangeLabel(int range)
std::string extrapNoiseEstimateRangeName(int range)
std::string extrapClosedLoopOlEstimateMethodElement(int method)
std::string extrapNoiseEstimateStatisticElement(int statistic)
std::string extrapPowerLawCrossoverModeElement(int mode)
std::string extrapNoiseEstimateRangeElement(int range)
static constexpr int c_olProcessLegacy
return handleExtrapNoiseEstimateDomainProperty(ipRecv)
static constexpr int c_extrapNoiseEstimateHighFreq
std::string extrapNoiseEstimateDomainName(int domain)
static constexpr int c_extrapPowerLawCrossoverManual
std::string extrapPowerLawCrossoverModeName(int mode)
static constexpr int c_olProcessNone
static constexpr int c_extrapNoiseEstimateMinimum
static constexpr int c_olProcessMoffatPeaks
return handleExtrapPowerLawCrossoverModeProperty(ipRecv)
static constexpr int c_extrapNoiseEstimatePercentile
static constexpr int c_extrapClosedLoopOlEstimateEtfOnly
std::string olProcessMethodElement(int method)
std::string extrapClosedLoopOlEstimateMethodName(int method)
static constexpr int c_extrapNoiseEstimateLowFreq
std::string extrapNoiseEstimateStatisticName(int statistic)
int extrapNoiseEstimateDomainFromElement(const std::string &element)
std::string extrapNoiseEstimateStatisticLabel(int statistic)
int extrapNoiseEstimateStatisticFromElement(const std::string &element)
int extrapNoiseEstimateRangeFromElement(const std::string &element)
std::string extrapPowerLawCrossoverModeLabel(int mode)
static constexpr int c_extrapNoiseEstimateOpenLoop
static constexpr int c_extrapNoiseEstimateClosedLoopPreXfer
static constexpr int c_extrapPowerLawCrossoverAutoSmoothedCrossing
int extrapClosedLoopOlEstimateMethodFromName(std::string method)
std::unique_lock< std::mutex > lock(m_indiMutex)
int extrapNoiseEstimateStatisticFromName(std::string statistic)
int extrapNoiseEstimateDomainFromName(std::string domain)
static constexpr int c_extrapClosedLoopOlEstimateNtfAware
std::string extrapNoiseEstimateDomainLabel(int domain)
std::string olProcessMethodLabel(int method)
std::string olProcessMethodName(int method)
int extrapNoiseEstimateRangeFromName(std::string range)
return handleExtrapMethodProperty(ipRecv)
Namespace for all libXWC tests.