LCOV - code coverage report
Current view: top level - include/ao/analysis - clAOLinearPredictor.hpp (source / functions) Coverage Total Hit
Test: mxlib Lines: 81.1 % 95 77
Test Date: 2026-08-30 22:23:37 Functions: 100.0 % 4 4

            Line data    Source code
       1              : /** \file clAOLinearPredictor.hpp
       2              :  * \author Jared R. Males (jaredmales@gmail.com)
       3              :  * \brief Provides a class to manage closed loop gain linear predictor determination.
       4              :  * \ingroup mxAO_files
       5              :  *
       6              :  */
       7              : 
       8              : #ifndef clAOLinearPredictor_hpp
       9              : #define clAOLinearPredictor_hpp
      10              : 
      11              : #include <cmath>
      12              : #include <cstddef>
      13              : #include <limits>
      14              : #include <vector>
      15              : 
      16              : #include "../../mxlib.hpp"
      17              : 
      18              : #include "../../math/floatUtils.hpp"
      19              : #include "../../math/geo.hpp"
      20              : 
      21              : #include "../../sigproc/psdUtils.hpp"
      22              : 
      23              : #include "../../sigproc/autocorrelation.hpp"
      24              : #include "../../sigproc/linearPredictor.hpp"
      25              : 
      26              : #include "clGainOpt.hpp"
      27              : 
      28              : namespace mx
      29              : {
      30              : namespace AO
      31              : {
      32              : namespace analysis
      33              : {
      34              : 
      35              : #define CLAOLP_BREADCRUMB
      36              : 
      37              : // #define CLAOLP_BREADCRUMB std::cerr << __FILE__ << ' ' << __LINE__ << '\n';
      38              : 
      39              : /// Class to manage the calculation of linear predictor coefficients for a closed-loop AO system.
      40              : /**
      41              :  * \tparam _realT the real floating point type in which to do all arithmetic.
      42              :  *
      43              :  * \ingroup mxAOAnalytic
      44              :  */
      45              : template <typename _realT>
      46              : struct clAOLinearPredictor
      47              : {
      48              :     typedef _realT realT; ///< Floating-point type used for predictor calculations.
      49              : 
      50              :   public:
      51              :     /// Result from one evaluated regularization scale.
      52              :     struct regResult
      53              :     {
      54              :         realT sc;   ///< Regularization scale in dB.
      55              :         realT gopt; ///< Optimum gain at this scale.
      56              :         realT gmax; ///< Maximum stable gain at this scale.
      57              :         realT var;  ///< Closed-loop variance at the optimum gain.
      58              :     };
      59              : 
      60              :     /// Termination state of the most recent regularization search.
      61              :     enum class regularizationStatus
      62              :     {
      63              :         notRun,             ///< No regularization search has been attempted.
      64              :         converged,          ///< The requested precision was reached.
      65              :         boundaryLimited,    ///< The optimum remained on the expanded search boundary.
      66              :         invalidControls,    ///< The configured search controls were invalid.
      67              :         iterationLimit,     ///< The search exhausted its iteration limit.
      68              :         calculationFailure, ///< Coefficient or gain calculation failed.
      69              :     };
      70              : 
      71              :     /// Diagnostic summary of the most recent regularization search.
      72              :     struct regularizationReport
      73              :     {
      74              :         regularizationStatus status{ regularizationStatus::notRun }; ///< Search termination state.
      75              :         int iterations{ 0 };                                         ///< Refinement iterations attempted.
      76              :         std::size_t evaluations{ 0 };                                ///< Regularization scales evaluated.
      77              :     };
      78              : 
      79              :     std::vector<realT> m_PSDtn;                     ///< Working memory for the regularized PSD
      80              : 
      81              :     std::vector<realT> m_psd2s;                     ///< Working memory for the 2-sided regularized PSD
      82              : 
      83              :     std::vector<realT> m_ac;                        ///< Working memory to hold the autocorrelation.
      84              : 
      85              :     sigproc::autocorrelationFromPSD<realT> m_acpsd; ///< Converts the working PSD to an autocorrelation.
      86              : 
      87              :     sigproc::linearPredictor<realT> m_lp;           ///< Linear predictor used to calculate coefficients.
      88              : 
      89              :     realT m_min_var0{ 0 };                          ///< Initial minimum variance, with zero requesting initialization.
      90              :     realT m_min_sc0{ 10 };                          ///< Initial minimum regularization scale in dB.
      91              :     realT m_precision0{ 2 };                        ///< Initial regularization scale spacing in dB.
      92              :     realT m_max_sc0{ 100 };                         ///< Initial maximum regularization scale in dB.
      93              :     realT m_dPrecision{ 3 };                        ///< Divisor applied to the spacing during refinement.
      94              : 
      95              :     realT m_gmax_lp{ 5 };                           ///< The maximum allowable gain for LP.
      96              : 
      97              :     // Stopping conditions:
      98              :     realT m_minPrecision{ 0.001 };               ///< Minimum requested regularization spacing in dB.
      99              :     int m_maxIts{ 100 };                         ///< Maximum number of search refinement iterations.
     100              : 
     101              :     int m_extrap{ 1 };                           ///< The LP extrapolation length in loop steps. Normally it is 1 step.
     102              : 
     103              :     std::vector<regResult> m_regResults;         ///< Per-scale telemetry collected when requested.
     104              : 
     105              :     regularizationReport m_regularizationReport; ///< Diagnostic summary of the latest search.
     106              : 
     107              :   public:
     108              :     /// Construct a closed-loop linear-predictor calculator with default search controls.
     109           20 :     clAOLinearPredictor() = default;
     110              : 
     111              :     /// Calculate the LP coefficients for a turbulence PSD and a noise PSD.
     112              :     /** This combines the two PSDs, augments to two-sided, and calls the linearPredictor.calcCoefficients method.
     113              :      *
     114              :      * A regularization constant can be added to the PSD as well.
     115              :      *
     116              :      * \returns `error_t::noerror` on success, otherwise `error_t::liberr`.
     117              :      */
     118          121 :     mx::error_t calcCoefficients( std::vector<realT> &PSDt, /**< [in] the turbulence PSD */
     119              :                                   std::vector<realT> &PSDn, /**< [in] the WFS noise PSD */
     120              :                                   realT PSDreg,             /**< [in] the regularizing constant. Set to 0 to not use. */
     121              :                                   int Nc,                   /**< [in] the number of LP coefficients */
     122              :                                   realT condition = 0       /**< [in] the condition number for the SVD.  If 0 then
     123              :                                                                       levinson recursion is used. */
     124              :     )
     125              :     {
     126              :         CLAOLP_BREADCRUMB;
     127          121 :         m_PSDtn.resize( PSDt.size() );
     128              : 
     129              :         CLAOLP_BREADCRUMB;
     130         7865 :         for( size_t i = 0; i < PSDt.size(); ++i )
     131              :         {
     132         7744 :             m_PSDtn[i] = PSDt[i] + PSDn[i] + PSDreg;
     133              :         }
     134              : 
     135              :         CLAOLP_BREADCRUMB;
     136          121 :         sigproc::augment1SidedPSD( m_psd2s, m_PSDtn, 1 );
     137              : 
     138              :         CLAOLP_BREADCRUMB;
     139          121 :         m_ac.resize( m_psd2s.size() );
     140              : 
     141              :         CLAOLP_BREADCRUMB;
     142          121 :         m_acpsd( m_ac, m_psd2s );
     143              :         CLAOLP_BREADCRUMB;
     144              : 
     145          121 :         if( m_lp.calcCoefficients( m_ac, Nc, m_extrap, condition ) != 0 )
     146              :         {
     147            0 :             return internal::mxlib_error_report( error_t::liberr, "linearPredictor::calcCoefficients failed" );
     148              :         }
     149              : 
     150          121 :         return error_t::noerror;
     151              :     }
     152              : 
     153              :     /// Worker function for regularizing the PSD for coefficient calculation.
     154              :     /**
     155              :      * \tparam telem if true then the results are collected in m_regResults.
     156              :      *
     157              :      * On first call (min_var = 0):
     158              :      *     loop over scale factors from min_sc to max_sc (<=) in steps of precision.
     159              :      *
     160              :      * On subsequent calls, when min_var and min_sc are passed back in
     161              :      *     loop over scale factors from min_sc-precision to max_sc in steps of
     162              :      *
     163              :      * \returns `error_t::noerror` on success, otherwise the coefficient-calculation error.
     164              :      */
     165              :     template <bool telem>
     166              :     mx::error_t
     167            6 :     _regularizeCoefficients( realT &min_var,  /**< [in,out] the minimum variance found; set to 0 on initial call */
     168              :                              realT &min_sc,   /**< [in,out] the scale factor at the minimum variance */
     169              :                              realT precision, /**< [in] the step size for the scale factor */
     170              :                              realT max_sc,    /**< [in] the maximum scale factor to test */
     171              :                              clGainOpt<realT> &go_lp,  /**< [in] the gain optimization object */
     172              :                              std::vector<realT> &PSDt, /**< [in] the turbulence PSD */
     173              :                              std::vector<realT> &PSDn, /**< [in] the WFS noise PSD */
     174              :                              int Nc                    /**< [in] the number of coefficients */
     175              :     )
     176              :     {
     177              :         CLAOLP_BREADCRUMB;
     178              : 
     179              :         realT gmax_lp;
     180              :         realT gopt_lp;
     181              :         realT var_lp;
     182              : 
     183              :         realT sc0;
     184              : 
     185            6 :         if( min_var == 0 ) // first call
     186              :         {
     187            3 :             sc0 = min_sc;
     188            3 :             min_var = std::numeric_limits<realT>::max();
     189              :         }
     190              :         else
     191              :         {
     192            3 :             sc0 = min_sc - precision * m_dPrecision;
     193              :         }
     194              : 
     195              :         CLAOLP_BREADCRUMB;
     196              : 
     197              :         // auto it = std::max_element(std::begin(PSDt), std::end(PSDt));
     198            6 :         realT psdReg = PSDt[0]; //*it/10;
     199              : 
     200              :         CLAOLP_BREADCRUMB;
     201              :         // Test from sc0 to max_sc in steps of precision
     202              :         // for( realT sc = sc0; sc <= max_sc; sc += precision )
     203          126 :         for( realT sc = max_sc; sc >= sc0; sc -= precision )
     204              :         {
     205              :             CLAOLP_BREADCRUMB;
     206          120 :             ++m_regularizationReport.evaluations;
     207          120 :             error_t rv = calcCoefficients( PSDt, PSDn, psdReg * pow( 10, -sc / 10 ), Nc );
     208          120 :             if( rv != error_t::noerror )
     209              :             {
     210            0 :                 m_regularizationReport.status = regularizationStatus::calculationFailure;
     211            0 :                 return rv;
     212              :             }
     213              : 
     214              :             CLAOLP_BREADCRUMB;
     215              : 
     216              :             CLAOLP_BREADCRUMB;
     217          120 :             go_lp.a( m_lp.m_c );
     218          120 :             go_lp.b( m_lp.m_c );
     219              : 
     220              :             CLAOLP_BREADCRUMB;
     221          120 :             rv = go_lp.maxStableGain( gmax_lp );
     222          120 :             if( rv != error_t::noerror )
     223              :             {
     224            0 :                 m_regularizationReport.status = regularizationStatus::calculationFailure;
     225            0 :                 return rv;
     226              :             }
     227          120 :             if( gmax_lp > m_gmax_lp )
     228              :             {
     229            0 :                 gmax_lp = m_gmax_lp;
     230              :             }
     231              : 
     232              :             CLAOLP_BREADCRUMB;
     233          120 :             rv = go_lp.optGainOpenLoop( gopt_lp, var_lp, PSDt, PSDn, gmax_lp, false );
     234          120 :             if( rv != error_t::noerror )
     235              :             {
     236            0 :                 m_regularizationReport.status = regularizationStatus::calculationFailure;
     237            0 :                 return rv;
     238              :             }
     239              : 
     240              :             if( telem )
     241              :             {
     242          120 :                 m_regResults.push_back( { sc, gopt_lp, gmax_lp, var_lp } );
     243              :             }
     244              : 
     245              :             CLAOLP_BREADCRUMB;
     246          120 :             if( var_lp < min_var )
     247              :             {
     248            6 :                 min_var = var_lp;
     249            6 :                 min_sc = sc;
     250              :             }
     251              : 
     252              :             // A jump by a factor of 10 indicates the wall
     253          120 :             if( var_lp > 10 * min_var )
     254              :             {
     255            0 :                 return error_t::noerror;
     256              :             }
     257              : 
     258              :             CLAOLP_BREADCRUMB;
     259              :         }
     260              : 
     261              :         CLAOLP_BREADCRUMB;
     262            6 :         return error_t::noerror;
     263              :     }
     264              : 
     265              :     /// Regularize the PSD and calculate the associated LP coefficients.
     266              :     /** The PSD is regularized by adding a constant to it.  This constant is found by minimizing the variance of the
     267              :      * residual PSD.
     268              :      *
     269              :      * \tparam telem if true then the results are collected in m_regResults
     270              :      *
     271              :      * \returns `error_t::noerror` for a converged or boundary-limited search, `error_t::invalidconfig` for invalid
     272              :      * controls, `error_t::timeout` on iteration exhaustion, or the coefficient-calculation error.
     273              :      */
     274              :     template <bool telem = false>
     275              :     mx::error_t
     276           17 :     regularizeCoefficients( realT &gmax_lp,           /**< [out] the maximum gain calculated for the regularized PSD */
     277              :                             realT &gopt_lp,           /**< [out] the optimum gain calculated for the regularized PSD */
     278              :                             realT &var_lp,            /**< [out] the variance at the optimum gain */
     279              :                             realT &min_sc,            /**< [out] the optimum regularization scale factor */
     280              :                             clGainOpt<realT> &go_lp,  /**< [in] the gain optimization object */
     281              :                             std::vector<realT> &PSDt, /**< [in] the turbulence PSD */
     282              :                             std::vector<realT> &PSDn, /**< [in] the WFS noise PSD */
     283              :                             int Nc                    /**< [in] the number of coefficients */
     284              :     )
     285              :     {
     286              :         CLAOLP_BREADCRUMB;
     287              : 
     288           17 :         m_regularizationReport = {};
     289              : 
     290           17 :         const realT intervalWidth = m_max_sc0 - m_min_sc0;
     291           33 :         if( !math::isFinite( m_min_sc0 ) || !math::isFinite( m_max_sc0 ) || !math::isFinite( m_precision0 ) ||
     292           13 :             !math::isFinite( m_minPrecision ) || !math::isFinite( m_dPrecision ) || m_minPrecision <= 0 ||
     293           37 :             m_precision0 <= m_minPrecision || intervalWidth <= 0 || m_precision0 > intervalWidth || m_dPrecision <= 1 ||
     294            4 :             m_maxIts <= 0 )
     295              :         {
     296           14 :             m_regularizationReport.status = regularizationStatus::invalidControls;
     297           28 :             return internal::mxlib_error_report( error_t::invalidconfig,
     298           42 :                                                  "invalid linear-predictor regularization search controls" );
     299              :         }
     300              : 
     301            3 :         realT min_var = m_min_var0;
     302            3 :         min_sc = m_min_sc0;
     303            3 :         realT precision = m_precision0;
     304            3 :         realT max_sc = m_max_sc0;
     305              : 
     306              :         if( telem )
     307              :         {
     308            3 :             m_regResults.reserve( m_maxIts * 50 );
     309              :         }
     310              : 
     311              :         CLAOLP_BREADCRUMB;
     312            3 :         int its = 0;
     313            9 :         while( precision > m_minPrecision && its < m_maxIts )
     314              :         {
     315              :             CLAOLP_BREADCRUMB;
     316            6 :             const bool firstIteration = its == 0;
     317            6 :             error_t rv = _regularizeCoefficients<telem>( min_var, min_sc, precision, max_sc, go_lp, PSDt, PSDn, Nc );
     318            6 :             ++its;
     319            6 :             m_regularizationReport.iterations = its;
     320            6 :             if( rv != error_t::noerror )
     321              :             {
     322            0 :                 return rv;
     323              :             }
     324              : 
     325              :             CLAOLP_BREADCRUMB;
     326            6 :             if( min_sc == max_sc )
     327              :             {
     328            3 :                 if( firstIteration )
     329              :                 {
     330            3 :                     min_sc -= precision;
     331            3 :                     max_sc = 200;
     332              :                 }
     333              :                 else
     334              :                 {
     335            0 :                     m_regularizationReport.status = regularizationStatus::boundaryLimited;
     336            0 :                     break;
     337              :                 }
     338              :             }
     339              :             else
     340              :             {
     341            3 :                 max_sc = min_sc + precision;
     342            3 :                 precision /= m_dPrecision;
     343              :             }
     344              :         }
     345              : 
     346            3 :         if( precision > m_minPrecision && its >= m_maxIts &&
     347            2 :             m_regularizationReport.status != regularizationStatus::boundaryLimited )
     348              :         {
     349            2 :             m_regularizationReport.status = regularizationStatus::iterationLimit;
     350            4 :             return internal::mxlib_error_report( error_t::timeout,
     351            6 :                                                  "linear-predictor regularization reached its iteration limit" );
     352              :         }
     353              : 
     354            1 :         if( m_regularizationReport.status != regularizationStatus::boundaryLimited )
     355              :         {
     356            1 :             m_regularizationReport.status = regularizationStatus::converged;
     357              :         }
     358              : 
     359              :         CLAOLP_BREADCRUMB;
     360              :         // Now record final values
     361            1 :         error_t rv = calcCoefficients( PSDt, PSDn, PSDt[0] * pow( 10, -min_sc / 10 ), Nc );
     362            1 :         if( rv != error_t::noerror )
     363              :         {
     364            0 :             m_regularizationReport.status = regularizationStatus::calculationFailure;
     365            0 :             return rv;
     366              :         }
     367              : 
     368              :         CLAOLP_BREADCRUMB;
     369            1 :         go_lp.a( m_lp.m_c );
     370            1 :         go_lp.b( m_lp.m_c );
     371              : 
     372              :         CLAOLP_BREADCRUMB;
     373            1 :         rv = go_lp.maxStableGain( gmax_lp );
     374            1 :         if( rv != error_t::noerror )
     375              :         {
     376            0 :             m_regularizationReport.status = regularizationStatus::calculationFailure;
     377            0 :             return rv;
     378              :         }
     379              : 
     380            1 :         rv = go_lp.optGainOpenLoop( gopt_lp, var_lp, PSDt, PSDn, gmax_lp, false );
     381            1 :         if( rv != error_t::noerror )
     382              :         {
     383            0 :             m_regularizationReport.status = regularizationStatus::calculationFailure;
     384            0 :             return rv;
     385              :         }
     386              : 
     387              :         CLAOLP_BREADCRUMB;
     388            1 :         return error_t::noerror;
     389              :     }
     390              : 
     391              :     /// Regularize the PSD and calculate the associated LP coefficients.
     392              :     /** The PSD is regularized by adding a constant to it.  This constant is found by minimizing the variance of the
     393              :      * residual PSD.
     394              :      *
     395              :      * \tparam printout if true then per-scale results are collected in m_regResults.
     396              :      *
     397              :      * \returns `error_t::noerror` on success, otherwise the regularization error.
     398              :      */
     399              :     template <bool printout = false>
     400              :     mx::error_t optimizeNc( realT &gmax_lp,           /**< [out] maximum gain for the selected predictor */
     401              :                             realT &gopt_lp,           /**< [out] optimum gain for the selected predictor */
     402              :                             int &Nc,                  /**< [out] selected number of coefficients */
     403              :                             realT &var_lp,            /**< [out] variance at the optimum gain */
     404              :                             clGainOpt<realT> &go_lp,  /**< [in] the gain optimization object */
     405              :                             std::vector<realT> &PSDt, /**< [in] the turbulence PSD */
     406              :                             std::vector<realT> &PSDn, /**< [in] the WFS noise PSD */
     407              :                             int minNc,                /**< [in] minimum number of coefficients */
     408              :                             int maxNc /**< [in] maximum number of coefficients */ )
     409              :     {
     410              :         realT minVar = std::numeric_limits<realT>::max();
     411              : 
     412              :         for( int n = minNc; n <= maxNc; ++n )
     413              :         {
     414              :             realT _gmax_lp;
     415              :             realT _gopt_lp;
     416              :             realT _var_lp;
     417              :             realT min_sc;
     418              :             error_t rv = regularizeCoefficients<printout>( _gmax_lp, _gopt_lp, _var_lp, min_sc, go_lp, PSDt, PSDn, n );
     419              :             if( rv != error_t::noerror )
     420              :             {
     421              :                 return rv;
     422              :             }
     423              : 
     424              :             if( _var_lp < minVar )
     425              :             {
     426              :                 gmax_lp = _gmax_lp;
     427              :                 gopt_lp = _gopt_lp;
     428              :                 var_lp = _var_lp;
     429              :                 Nc = n;
     430              : 
     431              :                 minVar = var_lp;
     432              :             }
     433              :         }
     434              : 
     435              :         return error_t::noerror;
     436              :     }
     437              : };
     438              : 
     439              : } // namespace analysis
     440              : } // namespace AO
     441              : } // namespace mx
     442              : 
     443              : #endif // clAOLinearPredictor_hpp
        

Generated by: LCOV version 2.0-1