LCOV - code coverage report
Current view: top level - include/ao/analysis - speckleAmpPSD.hpp (source / functions) Coverage Total Hit
Test: mxlib Lines: 0.0 % 37 0
Test Date: 2026-08-30 22:23:37 Functions: 0.0 % 1 0

            Line data    Source code
       1              : /** \file speckleAmpPSD.hpp
       2              :  * \author Jared R. Males (jaredmales@gmail.com)
       3              :  * \brief Calculates the PSD of speckle intensity given a modified Fourier mode amplitude PSD.
       4              :  * \ingroup mxAO_files
       5              :  *
       6              :  */
       7              : 
       8              : #ifndef speckleAmpPSD_hpp
       9              : #define speckleAmpPSD_hpp
      10              : 
      11              : #include <vector>
      12              : 
      13              : #include <Eigen/Dense>
      14              : 
      15              : #include "../../math/constants.hpp"
      16              : #include "../../math/randomT.hpp"
      17              : #include "../../math/vectorUtils.hpp"
      18              : 
      19              : #include "../../sigproc/signalWindows.hpp"
      20              : #include "../../sigproc/psdUtils.hpp"
      21              : #include "../../sigproc/psdFilter.hpp"
      22              : #include "../../sigproc/psdVarMean.hpp"
      23              : #include "../../sigproc/averagePeriodogram.hpp"
      24              : 
      25              : #include "jincFuncs.hpp"
      26              : 
      27              : namespace mx
      28              : {
      29              : namespace AO
      30              : {
      31              : namespace analysis
      32              : {
      33              : 
      34              : /// Calculate the PSD of the speckle intensity given the PSD of Fourier mode amplitude
      35              : /**
      36              :  * Does so by generating a time-series from the PSD using Fourier-domain convolution, and calculating
      37              :  * the average periodogram.
      38              :  *
      39              :  * A Hann window is used.
      40              :  *
      41              :  * Statistics are not currently normalized.  You need to normalize to match expected contrast if desired.
      42              :  *
      43              :  * Will also calculate the binned-variances in the generated time-series, if the arguments vars and bins are not null
      44              :  * pointers.
      45              :  *
      46              :  * \todo Figure out what's going on with psdFilter normalization!
      47              :  * \todo probably need to not do overlapped averaging in periodogram.  Could drop mean-sub then.
      48              :  */
      49              : template <typename realT>
      50            0 : int speckleAmpPSD(
      51              :     std::vector<realT> &spFreq, ///< [out] The frequency grid of the output PSD
      52              :     std::vector<realT>
      53              :         &spPSD, ///< [out] The speckle amplitude PSD corresponding to the freq coordinates.  Will be resized.
      54              :     const std::vector<realT> &freq,  ///< [in] The Frequency grid of the input PSD
      55              :     const std::vector<realT> &fmPSD, ///< [in] The Fourier mode PSD.
      56              :     const std::vector<std::complex<realT>>
      57              :         &fmXferFxn,                 ///< [in] The complex error transfer function, as a function of freq
      58              :     const std::vector<realT> &nPSD, ///< [in] The open-loop noise PSD
      59              :     const std::vector<std::complex<realT>> &nXferFxn, ///< [in] The noise transfer function, as a function of freq
      60              :     int N,                              ///< [in] The number of trials to use in calculating the amplitude PSD.
      61              :     std::vector<realT> *vars = nullptr, ///< [out] [optional] The binned variances of the time series generated.
      62              :     std::vector<realT> *bins = nullptr, ///< [in]  [optional] The bin sizes to use in calculating vars.
      63              :     bool noPSD = false ///< [in] [optional] if true then the PSD is not actually calculated, only the binned variances
      64              : )
      65              : {
      66              : 
      67            0 :     std::vector<realT> psd2, npsd2;
      68            0 :     std::vector<std::complex<realT>> xfer2, nxfer2;
      69              : 
      70            0 :     bool hasZero = false;
      71            0 :     if( freq[0] == 0 )
      72            0 :         hasZero = true;
      73              : 
      74              :     // First augment to two-sided DFT form
      75            0 :     mx::sigproc::augment1SidedPSD( psd2, fmPSD, !hasZero, 0.5 );
      76            0 :     mx::sigproc::augment1SidedPSD( npsd2, nPSD, !hasZero, 0.5 );
      77              : 
      78              :     // And augment the xfer fxns to two sided form
      79            0 :     sigproc::augment1SidedPSD( xfer2, fmXferFxn, !hasZero, 1.0 );
      80            0 :     sigproc::augment1SidedPSD( nxfer2, nXferFxn, !hasZero, 1.0 );
      81              : 
      82              :     // The time sampling
      83            0 :     realT dt = 1. / ( 2 * freq.back() );
      84              : 
      85              :     // Indices for getting the middle half
      86            0 :     int Nwd = 1.0 * psd2.size();
      87            0 :     int NwdStart = 0.5 * psd2.size() - 0.5 * Nwd;
      88              : 
      89            0 :     int Nsamp = 1.0 * psd2.size();
      90            0 :     int NsampStart = 0.5 * Nwd - 0.5 * Nsamp;
      91              : 
      92            0 :     sigproc::averagePeriodogram<realT> globalAvgPgram( Nsamp * 0.1, dt );
      93            0 :     spPSD.resize( globalAvgPgram.size() );
      94            0 :     for( size_t n = 0; n < spPSD.size(); ++n )
      95            0 :         spPSD[n] = 0;
      96              : 
      97            0 :     std::vector<std::vector<realT>> means;
      98            0 :     if( bins != nullptr && vars != nullptr )
      99            0 :         means.resize( bins->size() );
     100              : 
     101              :     // Calculate the Fourier Mode variance for normalization
     102            0 :     realT fmVar = sigproc::psdVar( freq, fmPSD );
     103              :     // and the noise variance
     104            0 :     realT nVar = sigproc::psdVar( freq, nPSD );
     105              : 
     106            0 : #pragma omp parallel
     107              :     {
     108              :         // Filters for imposing the PSDs
     109              :         mx::sigproc::psdFilter<realT, 1> filt;
     110              :         filt.psd( psd2, freq[1] - freq[0] );
     111              : 
     112              :         mx::sigproc::psdFilter<realT, 1> nfilt;
     113              :         nfilt.psd( npsd2, freq[1] - freq[0] );
     114              : 
     115              :         // FFTs for going to Fourier domain and back to time domain.
     116              :         math::ft::fftT<realT, std::complex<realT>, 1, 0> fft( psd2.size() );
     117              :         math::ft::fftT<std::complex<realT>, realT, 1, 0> fftB( psd2.size(), math::ft::dir::backward );
     118              : 
     119              :         // Fourier transform working memmory
     120              :         std::vector<std::complex<realT>> tform1( psd2.size() );
     121              :         std::vector<std::complex<realT>> tform2( psd2.size() );
     122              : 
     123              :         std::vector<std::complex<realT>> Ntform1( psd2.size() );
     124              :         std::vector<std::complex<realT>> Ntform2( psd2.size() );
     125              : 
     126              :         // Normally distributed random numbers
     127              :         math::normDistT<realT> normVar;
     128              :         normVar.seed();
     129              : 
     130              :         // The two modal amplitude series
     131              :         std::vector<realT> fm_n( psd2.size() );
     132              :         std::vector<realT> fm_nm( psd2.size() );
     133              : 
     134              :         // The two noise amplitude series
     135              :         std::vector<realT> N_n( psd2.size() );
     136              :         std::vector<realT> N_nm( psd2.size() );
     137              : 
     138              :         // The speckle amplitude
     139              :         std::vector<realT> vnl( Nwd );
     140              :         std::vector<realT> vn( Nsamp );
     141              : 
     142              :         // Periodogram averager
     143              :         sigproc::averagePeriodogram<realT> avgPgram( Nsamp * 0.1, dt ); //, 0, 1);
     144              :         avgPgram.win( sigproc::window::hann );
     145              : 
     146              :         // The temporary periodogram
     147              :         std::vector<realT> tpgram( avgPgram.size() ); // spPSD.size());
     148              : 
     149              : #pragma omp for
     150              :         for( int k = 0; k < N; ++k )
     151              :         {
     152              :             // Generate the time-series
     153              :             for( int i = 0; i < psd2.size(); ++i )
     154              :             {
     155              :                 fm_n[i] = normVar;
     156              :                 N_n[i] = normVar;
     157              :                 N_nm[i] = normVar;
     158              :             }
     159              : 
     160              :             // Filter and normalize the fourier mode time series
     161              :             filt( fm_n );
     162              :             math::vectorMeanSub( fm_n );
     163              :             realT actvar = math::vectorVariance( fm_n );
     164              :             realT norm = sqrt( fmVar / actvar );
     165              :             for( size_t q = 0; q < fm_n.size(); ++q )
     166              :                 fm_n[q] *= norm;
     167              : 
     168              :             // And move it to the Fourier domain
     169              :             fft( tform1.data(), fm_n.data() );
     170              : 
     171              :             // Filter and normalize the measurement mode time series
     172              :             nfilt.filter( N_n );
     173              :             nfilt.filter( N_nm );
     174              : 
     175              :             realT Nactvar = 0.5 * ( math::vectorVariance( N_n ) + math::vectorVariance( N_nm ) );
     176              :             norm = sqrt( nVar / Nactvar );
     177              :             for( size_t q = 0; q < fm_n.size(); ++q )
     178              :                 N_n[q] *= norm;
     179              :             for( size_t q = 0; q < fm_n.size(); ++q )
     180              :                 N_nm[q] *= norm;
     181              : 
     182              :             // And move them to the Fourier domain
     183              :             fft( Ntform1.data(), N_n.data() );
     184              :             fft( Ntform2.data(), N_nm.data() );
     185              : 
     186              :             std::complex<realT> scale = std::complex<realT>( ( tform1.size() ), 0 );
     187              : 
     188              :             // Apply the modal phase shift, and apply the measurement noise.
     189              :             for( size_t m = 0; m < tform1.size(); ++m )
     190              :             {
     191              :                 // Apply the phase shift to form the 2nd time series
     192              :                 tform2[m] = tform1[m] * exp( std::complex<realT>( 0, math::half_pi<realT>() ) );
     193              : 
     194              :                 // Apply the augmented ETF to two time-series
     195              :                 tform1[m] *= xfer2[m] / scale;
     196              :                 tform2[m] *= xfer2[m] / scale;
     197              : 
     198              :                 // Ntform2[m] = Ntform1[m]*exp( std::complex<realT>(0, math::half_pi<realT>() ));
     199              : 
     200              :                 Ntform1[m] *= nxfer2[m] / scale;
     201              :                 Ntform2[m] *= nxfer2[m] / scale;
     202              :             }
     203              : 
     204              :             //<<<<<<<<****** Transform back to the time domain.
     205              :             fftB( fm_n.data(), tform1.data() );
     206              :             fftB( fm_nm.data(), tform2.data() );
     207              :             fftB( N_n.data(), Ntform1.data() );
     208              :             fftB( N_nm.data(), Ntform2.data() );
     209              : 
     210              :             // Calculate the speckle amplitude and mean-subtract
     211              :             realT mn = 0;
     212              :             for( int i = 0; i < Nwd; ++i )
     213              :             {
     214              :                 realT h1 = fm_n[i + NwdStart] + N_n[i + NwdStart];
     215              :                 realT h2 = fm_nm[i + NwdStart] + N_nm[i + NwdStart];
     216              : 
     217              :                 vnl[i] = ( pow( h1, 2 ) + pow( h2, 2 ) );
     218              :                 mn += vnl[i];
     219              :             }
     220              :             mn /= vnl.size();
     221              : 
     222              :             // Extract middle sample and mean subtract
     223              :             for( int i = 0; i < Nsamp; ++i )
     224              :                 vn[i] = vnl[i + NsampStart] - mn;
     225              : 
     226              :             // Calculate PSD of the speckle amplitude
     227              :             if( !noPSD )
     228              :                 avgPgram( tpgram, vn );
     229              : 
     230              : // Accumulate
     231              : #pragma omp critical
     232              :             {
     233              :                 if( bins != nullptr && vars != nullptr )
     234              :                     math::vectorBinMeans( means, *bins, vn );
     235              :                 for( size_t i = 0; i < spPSD.size(); ++i )
     236              :                     spPSD[i] += tpgram[i];
     237              :             }
     238              :         }
     239              :     } // pragma omp parallel
     240              : 
     241            0 :     if( !noPSD )
     242              :     {
     243              :         //-- Calculate frequency grid (which is maybe not identical to input freq)
     244            0 :         realT df = ( 1.0 ) / ( 2.0 * spPSD.size() * dt );
     245            0 :         spFreq.resize( spPSD.size() );
     246            0 :         for( size_t i = 0; i < spFreq.size(); ++i )
     247              :         {
     248            0 :             spFreq[i] = globalAvgPgram[i];
     249            0 :             spPSD[i] /= N;
     250              :         }
     251              :         // realT spVar = sigproc::psdVar1sided(df, spPSD.data(), spPSD.size());
     252              :         // for(size_t i=0; i< spPSD.size(); ++i) spPSD[i] *= (fmVar*fmVar/spVar);
     253              :     }
     254              : 
     255              :     // Calculate binned variances.
     256            0 :     if( bins != nullptr && vars != nullptr )
     257              :     {
     258            0 :         vars->resize( bins->size() );
     259            0 :         for( size_t i = 0; i < bins->size(); ++i )
     260              :         {
     261            0 :             ( *vars )[i] = math::vectorVariance( means[i] );
     262              :         }
     263              :     }
     264              : 
     265            0 :     return 0;
     266            0 : }
     267              : 
     268              : /// Calculate the variance of the mean vs bin size in a speckle time-series
     269              : /**
     270              :  * Does so by generating a time-series from the PSD using Fourier-domain convolution, then
     271              :  * calculates the binned-variances in the generated time-series.
     272              :  *
     273              :  * This is just a wrapper for speckleAmpPSD, with no actual PSD calculation.
     274              :  *
     275              :  */
     276              : template <typename realT>
     277              : int speckleAmpVarMean(
     278              :     std::vector<realT> &vars,                    ///< [out] The binned variances of the time series generated.
     279              :     std::vector<realT> &bins,                    ///< [in] The bin sizes to use to calculate the variances.
     280              :     std::vector<realT> &freq,                    ///< [in] The Frequency grid of the input PSD
     281              :     std::vector<realT> &fmPSD,                   ///< [in] The open-loop Fourier mode PSD.
     282              :     std::vector<std::complex<realT>> &fmXferFxn, ///< [in] The complex error transfer function, as a function of freq
     283              :     std::vector<realT> &nPSD,                    ///< [in] The open-loop noise PSD
     284              :     std::vector<std::complex<realT>> &nXferFxn,  ///< [in] The noise transfer function, as a function of freq
     285              :     int N ///< [in] The number of trials to use in calculating the amplitude time-series.
     286              : )
     287              : {
     288              :     std::vector<realT> spFreq, spPSD;
     289              : 
     290              :     return speckleAmpPSD( spFreq, spPSD, freq, fmPSD, fmXferFxn, nPSD, nXferFxn, N, &vars, &bins, true );
     291              : }
     292              : 
     293              : } // namespace analysis
     294              : } // namespace AO
     295              : } // namespace mx
     296              : 
     297              : #endif // speckleAmpPSD_hpp
        

Generated by: LCOV version 2.0-1