38TEST_CASE(
"Gain optimizer recomputes transfer functions after changing Ti",
"[ao::analysis::clGainOpt]" )
40 constexpr double initialTi = 0.001;
41 constexpr double updatedTi = 0.0017;
42 constexpr double delay = 0.0013;
43 constexpr double gain = 0.42;
45 const std::vector<double> frequency{ 25.0, 50.0, 75.0, 100.0 };
46 const std::vector<double> fir{ 0.75, 0.20, -0.05 };
47 const std::vector<double> iir{ 0.45, -0.08, 0.025 };
49 optimizerT retimed( initialTi, delay );
50 retimed.f( frequency );
53 retimed.remember( 0.97 );
55 for( std::size_t index = 0; index < frequency.size(); ++index )
57 retimed.olXfer( index );
60 retimed.Ti( updatedTi );
62 optimizerT fresh( updatedTi, delay );
66 fresh.remember( 0.97 );
68 for( std::size_t index = 0; index < frequency.size(); ++index )
70 CAPTURE( index, frequency[index] );
72 optimizerT::complexT retimedDm;
73 optimizerT::complexT retimedDelay;
74 optimizerT::complexT retimedController;
75 const optimizerT::complexT retimedOpenLoop =
76 retimed.olXfer( index, retimedDm, retimedDelay, retimedController );
78 optimizerT::complexT freshDm;
79 optimizerT::complexT freshDelay;
80 optimizerT::complexT freshController;
81 const optimizerT::complexT freshOpenLoop = fresh.olXfer( index, freshDm, freshDelay, freshController );
83 requireComplexEqual( retimedDm, freshDm );
84 requireComplexEqual( retimedDelay, freshDelay );
85 requireComplexEqual( retimedController, freshController );
86 requireComplexEqual( retimedOpenLoop, freshOpenLoop );
87 requireComplexEqual( retimed.clETF( index, gain ), fresh.clETF( index, gain ) );
88 REQUIRE( retimed.clETFPhase( index, gain ) ==
89 Approx( fresh.clETFPhase( index, gain ) ).epsilon( 1e-12 ).margin( 1e-14 ) );
90 REQUIRE( retimed.clETF2( index, gain ) ==
91 Approx( fresh.clETF2( index, gain ) ).epsilon( 1e-12 ).margin( 1e-14 ) );
92 requireComplexEqual( retimed.clNTF( index, gain ), fresh.clNTF( index, gain ) );
93 REQUIRE( retimed.clNTF2( index, gain ) ==
94 Approx( fresh.clNTF2( index, gain ) ).epsilon( 1e-12 ).margin( 1e-14 ) );
96 double retimedEtf = 0;
97 double retimedNtf = 0;
98 retimed.clTF2( retimedEtf, retimedNtf, index, gain );
102 fresh.clTF2( freshEtf, freshNtf, index, gain );
104 REQUIRE( retimedEtf == Approx( freshEtf ).epsilon( 1e-12 ).margin( 1e-14 ) );
105 REQUIRE( retimedNtf == Approx( freshNtf ).epsilon( 1e-12 ).margin( 1e-14 ) );
108 const std::vector<double> disturbancePsd{ 4.0, 2.0, 0.7, 0.2 };
109 const std::vector<double> noisePsd{ 0.01, 0.01, 0.01, 0.01 };
110 REQUIRE( retimed.clVariance( disturbancePsd, noisePsd, gain ) ==
111 Approx( fresh.clVariance( disturbancePsd, noisePsd, gain ) ).epsilon( 1e-12 ).margin( 1e-14 ) );
113 double retimedOptimalVariance = 0;
114 double retimedOptimalGain = 0;
116 retimed.optGainOpenLoop( retimedOptimalGain, retimedOptimalVariance, disturbancePsd, noisePsd, 0.5,
false ) ==
119 double freshOptimalVariance = 0;
120 double freshOptimalGain = 0;
121 REQUIRE( fresh.optGainOpenLoop( freshOptimalGain, freshOptimalVariance, disturbancePsd, noisePsd, 0.5,
false ) ==
124 REQUIRE( retimedOptimalGain == Approx( freshOptimalGain ).epsilon( 1e-12 ).margin( 1e-14 ) );
125 REQUIRE( retimedOptimalVariance == Approx( freshOptimalVariance ).epsilon( 1e-12 ).margin( 1e-14 ) );
133TEST_CASE(
"Gain optimizer interpolates a pure-integrator stability crossing",
"[ao::analysis::clGainOpt]" )
135 optimizerT optimizer( 0.001, 0.0015 );
136 const std::vector<double> frequency{ 100.0, 124.0, 126.0, 150.0 };
137 optimizer.f( frequency );
139 const double crossingPhase = std::numbers::pi_v<double> / 4.0;
140 const double expectedGain = crossingPhase * crossingPhase / ( 2.0 * std::sin( crossingPhase / 2.0 ) );
141 const double lowerSampleGain = -1.0 / optimizer.olXfer( 1 ).real();
143 double maximumGain = 0;
144 optimizerT::maxStableGainReport report;
147 REQUIRE( report.status == optimizerT::maxStableGainStatus::crossingFound );
148 REQUIRE( report.lowerIndex == 1 );
149 REQUIRE( report.upperIndex == 2 );
150 REQUIRE( report.lowerFrequency == 124.0 );
151 REQUIRE( report.upperFrequency == 126.0 );
152 REQUIRE( report.crossingFrequency == Approx( 125.0 ).margin( 0.02 ) );
153 REQUIRE( maximumGain == report.gain );
154 REQUIRE( std::abs( maximumGain - expectedGain ) < std::abs( lowerSampleGain - expectedGain ) );
186TEST_CASE(
"Gain optimizer reports minimizer termination",
"[ao::analysis::clGainOpt]" )
188 optimizerT optimizer( 0.001, 0.0015 );
189 std::vector<double> frequency;
190 std::vector<double> disturbance;
191 std::vector<double> noise;
192 for(
int index = 1; index <= 500; ++index )
194 const double value =
static_cast<double>( index );
195 frequency.push_back( value );
196 disturbance.push_back( 1.0 / ( 1.0 + value * value ) );
197 noise.push_back( 1e-6 );
199 optimizer.f( frequency );
201 double optimalGain = 0;
203 optimizerT::optGainReport report;
204 constexpr double smallMaximumGain = 1e-3;
205 REQUIRE( optimizer.optGainOpenLoop( optimalGain, variance, disturbance, noise, smallMaximumGain,
true, &report ) ==
207 REQUIRE( ( report.status == optimizerT::optGainStatus::converged ||
208 report.status == optimizerT::optGainStatus::boundaryLimited ) );
209 REQUIRE( report.minimumEvaluatedGain >= optimizer.m_minFindMin );
210 REQUIRE( report.maximumEvaluatedGain <= optimizer.m_minFindMaxFact * smallMaximumGain );
214 REQUIRE( optimizer.optGainOpenLoop( optimalGain, variance, disturbance, noise, 1e-10,
false, &report ) ==
216 REQUIRE( report.status == optimizerT::optGainStatus::invalidInput );
220 optimizer.f( std::vector<double>{ 1.0, 2.0, 3.0 } );
221 disturbance.resize( 3 );
223 REQUIRE( optimizer.optGainOpenLoop( optimalGain, variance, disturbance, noise,
false, &report ) ==
225 REQUIRE( report.status == optimizerT::optGainStatus::stabilityFailure );
226 REQUIRE( report.stability.status == optimizerT::maxStableGainStatus::noCrossing );
228 optimizer.f( frequency );
229 disturbance.resize( frequency.size(), 1e-3 );
230 noise.resize( frequency.size(), 1e-6 );
231 optimizer.m_minFindMaxIter = 1;
232 REQUIRE( optimizer.optGainOpenLoop( optimalGain, variance, disturbance, noise, 0.5,
false, &report ) ==
234 REQUIRE( report.status == optimizerT::optGainStatus::iterationLimit );
235 REQUIRE( report.iterations == 1 );