LCOV - code coverage report
Current view: top level - sigproc - psdUtils.hpp (source / functions) Coverage Total Hit
Test: mxlib Lines: 84.7 % 157 133
Test Date: 2026-02-19 16:58:26 Functions: 100.0 % 11 11

            Line data    Source code
       1              : /** \file psdUtils.hpp
       2              :  * \brief Tools for working with PSDs
       3              :  *
       4              :  * \author Jared R. Males (jaredmales@gmail.com)
       5              :  *
       6              :  * \ingroup signal_processing_files
       7              :  *
       8              :  */
       9              : 
      10              : //***********************************************************************//
      11              : // Copyright 2015-2021 Jared R. Males (jaredmales@gmail.com)
      12              : //
      13              : // This file is part of mxlib.
      14              : //
      15              : // mxlib is free software: you can redistribute it and/or modify
      16              : // it under the terms of the GNU General Public License as published by
      17              : // the Free Software Foundation, either version 3 of the License, or
      18              : // (at your option) any later version.
      19              : //
      20              : // mxlib is distributed in the hope that it will be useful,
      21              : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      22              : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      23              : // GNU General Public License for more details.
      24              : //
      25              : // You should have received a copy of the GNU General Public License
      26              : // along with mxlib.  If not, see <http://www.gnu.org/licenses/>.
      27              : //***********************************************************************//
      28              : 
      29              : #ifndef psdUtils_hpp
      30              : #define psdUtils_hpp
      31              : 
      32              : #ifndef EIGEN_NO_CUDA
      33              :     #define EIGEN_NO_CUDA
      34              : #endif
      35              : 
      36              : #include <type_traits>
      37              : 
      38              : #include <Eigen/Dense>
      39              : 
      40              : #include <iostream>
      41              : 
      42              : #include "../mxlib.hpp"
      43              : 
      44              : #include "../math/ft/fftT.hpp"
      45              : #include "../math/vectorUtils.hpp"
      46              : 
      47              : namespace mx
      48              : {
      49              : namespace sigproc
      50              : {
      51              : 
      52              : /** \ingroup psds
      53              :  * @{
      54              :  */
      55              : 
      56              : /// Calculate the variance of a 1-D, 1-sided PSD
      57              : /** By default uses trapezoid rule integration.  This can be changed to mid-point integration.
      58              :  *
      59              :  * \returns the variance of a PSD (the integral).
      60              :  *
      61              :  * \tparam realT the real floating point type
      62              :  *
      63              :  * \test Scenario: calculating variance from a 1D PSD \ref tests_sigproc_psdUtils_psdVar_1D "[test doc]"
      64              :  */
      65              : template <typename realT>
      66            3 : realT psdVar1sided( realT df,         ///< [in] the frequency scale of the PSD
      67              :                     const realT *PSD, ///< [in] the PSD to integrate.
      68              :                     size_t sz,        ///< [in] the size of the PSD vector
      69              :                     realT half = 0.5  ///< [in] [optional] controls if trapezoid (0.5) or mid-point (1.0) integration is
      70              :                                       ///< used.  Do not use other values.
      71              : )
      72              : {
      73            3 :     realT var = 0;
      74              : 
      75            3 :     var = half * PSD[0];
      76              : 
      77           12 :     for( size_t i = 1; i < sz - 1; ++i )
      78              :     {
      79            9 :         var += PSD[i];
      80              :     }
      81              : 
      82            3 :     var += half * PSD[sz - 1];
      83              : 
      84            3 :     var *= df;
      85              : 
      86            3 :     return var;
      87              : }
      88              : 
      89              : /// Calculate the variance of a 1-D, 2-sided PSD
      90              : /** By default uses trapezoid rule integration.  This can be changed to mid-point integration.
      91              :  *
      92              :  * Assumes the 2-sided PSD is in standard FFT storage order, and that sz is even.
      93              :  *
      94              :  * \returns the variance of a PSD (the integral).
      95              :  *
      96              :  * \tparam realT the real floating point type
      97              :  *
      98              :  * \test Scenario: calculating variance from a 1D PSD. \ref tests_sigproc_psdUtils_psdVar_1D "[test doc]"
      99              :  */
     100              : template <typename realT>
     101            3 : realT psdVar2sided( realT df,         ///< [in] the frequency scale of the PSD
     102              :                     const realT *PSD, ///< [in] the PSD to integrate.
     103              :                     size_t sz,        ///< [in] the size of the PSD vector
     104              :                     realT half = 0.5  ///< [in] [optional] controls if trapezoid (0.5) or mid-point (1.0) integration is
     105              :                                       ///< used.  Do not use other values.
     106              : )
     107              : {
     108            3 :     realT var = 0;
     109              : 
     110            3 :     var = PSD[0];
     111              : 
     112            3 :     size_t i = 1;
     113           10 :     for( ; i < sz / 2; ++i )
     114              :     {
     115            7 :         var += PSD[i];
     116            7 :         var += PSD[sz - i];
     117              :     }
     118            3 :     var += half * PSD[i]; // The mid-point is double.  It is also the endpoint of integration from each side, so it
     119              :                           // would enter twice, hence once here.
     120              : 
     121            3 :     var *= df;
     122              : 
     123            3 :     return var;
     124              : }
     125              : 
     126              : /// Calculate the variance of a 1-D PSD
     127              : /** By default uses trapezoid rule integration.  This can be changed to mid-point integration.
     128              :  *
     129              :  * If f.back() < 0, then a 2-sided PSD in FFT storage order is assumed.  Otherwise, PSD is treated as 1-sided.
     130              :  *
     131              :  * \returns the variance of a PSD (the integral).
     132              :  *
     133              :  * \tparam realT the real floating point type
     134              :  *
     135              :  * \test Scenario: calculating variance from a 1D PSD. \ref tests_sigproc_psdUtils_psdVar_1D "[test doc]"
     136              :  */
     137              : template <typename realT>
     138            6 : realT psdVar( const std::vector<realT> &f,   ///< [in] the frequency scale of the PSD.
     139              :               const std::vector<realT> &PSD, ///< [in] the PSD to integrate.
     140              :               realT half = 0.5 ///< [in] [optional] controls if trapezoid (0.5) or mid-point (1.0) integration is used.
     141              :                                ///< Do not use other values.
     142              : )
     143              : {
     144            6 :     if( f.back() < 0 )
     145              :     {
     146            3 :         return psdVar2sided( f[1] - f[0], PSD.data(), PSD.size(), half );
     147              :     }
     148              :     else
     149              :     {
     150            3 :         return psdVar1sided( f[1] - f[0], PSD.data(), PSD.size(), half );
     151              :     }
     152              : }
     153              : 
     154              : /// Calculate the variance of a PSD
     155              : /** By default uses trapezoid rule integration.  This can be changed to mid-point integration.
     156              :  *
     157              :  * \overload
     158              :  *
     159              :  * \returns the variance of a PSD (the integral).
     160              :  *
     161              :  * \tparam realT the real floating point type
     162              :  */
     163              : template <typename eigenArrT>
     164              : typename eigenArrT::Scalar psdVarDisabled(
     165              :     eigenArrT &freq, ///< [in] the frequency scale of the PSD
     166              :     eigenArrT &PSD,  ///< [in] the PSD to integrate.
     167              :     bool trap = true ///< [in] [optional] controls if trapezoid (true) or mid-point (false) integration is used.
     168              : )
     169              : {
     170              :     typename eigenArrT::Scalar half = 0.5;
     171              :     if( !trap )
     172              :         half = 1.0;
     173              : 
     174              :     typename eigenArrT::Scalar var = 0;
     175              : 
     176              :     var = half * PSD( 0, 0 );
     177              : 
     178              :     for( int i = 1; i < freq.rows() - 1; ++i )
     179              :         var += PSD( i, 0 );
     180              : 
     181              :     var += half * PSD( freq.rows() - 1, 0 );
     182              : 
     183              :     var *= ( freq( 1, 0 ) - freq( 0, 0 ) );
     184              : 
     185              :     return var;
     186              : }
     187              : 
     188              : /// Calculates the frequency sampling for a grid given maximum dimension and maximum frequency.
     189              : /** The freq_sampling is
     190              :  * @f$ \Delta f = f_{max}/ (0.5*dim) @f$
     191              :  * where @f$ f_{max} = 1/(2\Delta t) @f$ is the maximum frequency and @f$ dim @f$ is the size of the grid.
     192              :  *
     193              :  * \param [in] dim is the size of the grid
     194              :  * \param [in] f_max is the maximum frequency of the grid
     195              :  *
     196              :  * \returns the sampling interval @f$ \delta f @f$
     197              :  *
     198              :  * \tparam realT is the real floating point type used for calculations.
     199              :  *
     200              :  */
     201              : template <class realT>
     202            4 : realT freq_sampling( size_t dim, realT f_max )
     203              : {
     204            4 :     return ( f_max / ( 0.5 * dim ) );
     205              : }
     206              : 
     207              : #if 0
     208              : ///Create a 1-D frequency grid
     209              : /**
     210              :   * \param [out] vec the pre-allocated Eigen-type 1xN or Nx1 array, on return contains the frequency grid
     211              :   * \param [in] dt the temporal sampling of the time series
     212              :   * \param [in] inverse [optional] if true
     213              :   *
     214              :   * \tparam eigenArr the Eigen-like array type
     215              :   */
     216              : template<typename eigenArr>
     217              : void frequency_grid1D( eigenArr & vec,
     218              :                        typename eigenArr::Scalar dt,
     219              :                        bool inverse = false )
     220              : {
     221              :    typename eigenArr::Index dim, dim_1, dim_2;
     222              :    typename eigenArr::Scalar df;
     223              : 
     224              :    dim_1 = vec.rows();
     225              :    dim_2 = vec.cols();
     226              : 
     227              :    dim = std::max(dim_1, dim_2);
     228              : 
     229              :    df = freq_sampling(dim, 0.5/dt);
     230              : 
     231              :    if( !inverse )
     232              :    {
     233              :       for(int ii=0; ii < ceil(0.5*(dim-1) + 1); ++ii)
     234              :       {
     235              :          vec(ii) = ii*df;
     236              :       }
     237              : 
     238              :       for(int ii=ceil(0.5*(dim-1)+1); ii < dim_1; ++ii)
     239              :       {
     240              :          vec(ii) = (ii-dim)*df;
     241              :       }
     242              :    }
     243              :    else
     244              :    {
     245              :       for(int ii=0; ii < dim; ++ii)
     246              :       {
     247              :          vec(ii) = df * ii / dim;
     248              :       }
     249              :    }
     250              : }
     251              : #endif
     252              : 
     253              : /// Create a 1-D frequency grid
     254              : /**
     255              :  *
     256              :  * \tparam realT a real floating point type
     257              :  * \tparam realParamT a real floating point type, convenience to avoid double-float confusion.
     258              :  *
     259              :  * \test Verify creation of a 1D frequency grid \ref tests_sigproc_psdUtils_frequencyGrid_1D "[test doc]"
     260              :  */
     261              : template <typename realT, typename realParamT>
     262            4 : int frequencyGrid(
     263              :     std::vector<realT> &vec, ///< [out] vec the pre-allocated vector, on return contains the frequency grid
     264              :     realParamT dt,           ///< [in] dt the temporal sampling of the time series
     265              :     bool fftOrder = true     ///< [in] fftOrder [optional] if true the frequency grid is in FFT order
     266              : )
     267              : {
     268            4 :     realT dtTT = dt;
     269              : 
     270            4 :     if( fftOrder )
     271              :     {
     272            2 :         if( vec.size() % 2 == 1 )
     273              :         {
     274            0 :             internal::mxlib_error_report(error_t::invalidarg,"Frequency scale can't be odd-sized for FFT order" );
     275            0 :             return -1;
     276              :         }
     277              : 
     278            2 :         realT df = ( 1.0 / dtTT ) / ( (realT)vec.size() );
     279              : 
     280           14 :         for( ssize_t ii = 0; ii < ceil( 0.5 * ( vec.size() - 1 ) + 1 ); ++ii )
     281              :         {
     282           12 :             vec[ii] = ii * df;
     283              :         }
     284              : 
     285           10 :         for( ssize_t ii = ceil( 0.5 * ( vec.size() - 1 ) + 1 ); ii < vec.size(); ++ii )
     286              :         {
     287            8 :             vec[ii] = ( ii - (ssize_t)vec.size() ) * df;
     288              :         }
     289              : 
     290            2 :         return 0;
     291              :     }
     292              :     else
     293              :     {
     294            2 :         if( vec.size() % 2 == 0 )
     295              :         {
     296            1 :             realT df = ( 0.5 / dtTT ) / ( (realT)vec.size() - 1 );
     297            7 :             for( int ii = 0; ii < vec.size(); ++ii )
     298              :             {
     299            6 :                 vec[ii] = df * ii;
     300              :             }
     301              : 
     302            1 :             return 0;
     303              :         }
     304              :         else
     305              :         {
     306            1 :             realT df = ( 0.5 / dt ) / ( (realT)vec.size() );
     307            6 :             for( int ii = 0; ii < vec.size(); ++ii )
     308              :             {
     309            5 :                 vec[ii] = df * ( ii + 1 );
     310              :             }
     311              : 
     312            1 :             return 0;
     313              :         }
     314              :     }
     315              : }
     316              : 
     317              : /// Create a 2-D frequency grid
     318              : template <typename eigenArr, typename realParamT>
     319            4 : void frequencyGrid( eigenArr &arr, realParamT drT, eigenArr *k_x, eigenArr *k_y )
     320              : {
     321            4 :     typename eigenArr::Scalar dr = drT;
     322              : 
     323              :     typename eigenArr::Index dim_1, dim_2;
     324              :     typename eigenArr::Scalar k_1, k_2, df;
     325              : 
     326            4 :     dim_1 = arr.rows();
     327            4 :     dim_2 = arr.cols();
     328              : 
     329            4 :     if( k_x )
     330            0 :         k_x->resize( dim_1, dim_2 );
     331            4 :     if( k_y )
     332            0 :         k_y->resize( dim_1, dim_2 );
     333              : 
     334            4 :     df = freq_sampling( std::max( dim_1, dim_2 ), 0.5 / dr );
     335              : 
     336          104 :     for( int ii = 0; ii < 0.5 * ( dim_1 - 1 ) + 1; ++ii )
     337              :     {
     338          100 :         k_1 = ii * df;
     339         2856 :         for( int jj = 0; jj < 0.5 * ( dim_2 - 1 ) + 1; ++jj )
     340              :         {
     341         2756 :             k_2 = jj * df;
     342              : 
     343         2756 :             arr( ii, jj ) = sqrt( k_1 * k_1 + k_2 * k_2 );
     344              : 
     345         2756 :             if( k_x )
     346            0 :                 ( *k_x )( ii, jj ) = k_1;
     347         2756 :             if( k_x )
     348            0 :                 ( *k_y )( ii, jj ) = k_2;
     349              :         }
     350              : 
     351         2756 :         for( int jj = 0.5 * ( dim_2 - 1 ) + 1; jj < dim_2; ++jj )
     352              :         {
     353         2656 :             k_2 = ( jj - dim_2 ) * df;
     354              : 
     355         2656 :             arr( ii, jj ) = sqrt( k_1 * k_1 + k_2 * k_2 );
     356              : 
     357         2656 :             if( k_x )
     358            0 :                 ( *k_x )( ii, jj ) = k_1;
     359         2656 :             if( k_x )
     360            0 :                 ( *k_y )( ii, jj ) = k_2;
     361              :         }
     362              :     }
     363              : 
     364          100 :     for( int ii = 0.5 * ( dim_1 - 1 ) + 1; ii < dim_1; ++ii )
     365              :     {
     366           96 :         k_1 = ( ii - dim_1 ) * df;
     367         2752 :         for( int jj = 0; jj < 0.5 * ( dim_2 - 1 ) + 1; ++jj )
     368              :         {
     369         2656 :             k_2 = jj * df;
     370              : 
     371         2656 :             arr( ii, jj ) = sqrt( k_1 * k_1 + k_2 * k_2 );
     372              : 
     373         2656 :             if( k_x )
     374            0 :                 ( *k_x )( ii, jj ) = k_1;
     375         2656 :             if( k_x )
     376            0 :                 ( *k_y )( ii, jj ) = k_2;
     377              :         }
     378              : 
     379         2656 :         for( int jj = 0.5 * ( dim_2 - 1 ) + 1; jj < dim_2; ++jj )
     380              :         {
     381         2560 :             k_2 = ( jj - dim_2 ) * df;
     382              : 
     383         2560 :             arr( ii, jj ) = sqrt( k_1 * k_1 + k_2 * k_2 );
     384              : 
     385         2560 :             if( k_x )
     386            0 :                 ( *k_x )( ii, jj ) = k_1;
     387         2560 :             if( k_x )
     388            0 :                 ( *k_y )( ii, jj ) = k_2;
     389              :         }
     390              :     }
     391            4 : }
     392              : 
     393              : /// Create a frequency grid
     394              : template <typename eigenArr>
     395            4 : void frequencyGrid( eigenArr &arr, typename eigenArr::Scalar dt )
     396              : {
     397            4 :     frequencyGrid( arr, dt, (eigenArr *)0, (eigenArr *)0 );
     398            4 : }
     399              : 
     400              : /// Create a frequency grid
     401              : template <typename eigenArr>
     402              : void frequencyGrid( eigenArr &arr, typename eigenArr::Scalar dt, eigenArr &k_x, eigenArr &k_y )
     403              : {
     404              :     frequencyGrid( arr, dt, &k_x, &k_y );
     405              : }
     406              : 
     407              : /// Calculate the normalization for a 1-D @f$ 1/|f|^\alpha @f$ PSD.
     408              : /**
     409              :  * \param [in] fmin is the minimum non-zero absolute value of frequency
     410              :  * \param [in] fmax is the maximum absolute value of frequencey
     411              :  * \param [in] alpha is the power-law exponent, by convention @f$ \alpha > 0 @f$.
     412              :  *
     413              :  * \returns the normalization for a 2-sided power law PSD.
     414              :  *
     415              :  * \tparam realT is the real floating point type used for calculations.
     416              :  */
     417              : template <typename realT>
     418              : realT oneoverf_norm( realT fmin, realT fmax, realT alpha )
     419              : {
     420              :     realT integ = 2 * ( pow( fmax, -1.0 * alpha + 1.0 ) - pow( fmin, -1.0 * alpha + 1.0 ) ) / ( -1.0 * alpha + 1.0 );
     421              : 
     422              :     return 1 / integ;
     423              : }
     424              : 
     425              : /// Calculate the normalization for a 2-D @f$ 1/|k|^\alpha @f$ PSD.
     426              : /**
     427              :  * \param [in] kmin is the minimum non-zero absolute value of frequency
     428              :  * \param [in] kmax is the maximum absolute value of frequencey
     429              :  * \param [in] alpha is the power-law exponent, by convention @f$ \alpha > 0 @f$.
     430              :  *
     431              :  * \returns the normalization for a 2-D, 2-sided power law PSD.
     432              :  *
     433              :  * \tparam realT is the real floating point type used for calculations.
     434              :  */
     435              : template <typename realT>
     436              : realT oneoverk_norm( realT kmin, realT kmax, realT alpha )
     437              : {
     438              :     realT integ = 2 * ( pow( kmax, -1 * alpha + 2.0 ) - pow( kmin, -1.0 * alpha + 2.0 ) ) / ( -1 * alpha + 2.0 );
     439              : 
     440              :     return 1 / integ;
     441              : }
     442              : 
     443              : /// Normalize a 1-D PSD to have a given variance
     444              : /** A frequency range can be specified to calculate the norm, otherwise f[0] to f[f.size()-1] is the range.  The entire
     445              :  * PSD is normalized regardless.
     446              :  *
     447              :  * \tparam floatT the floating point type of the PSD.
     448              :  * \tparam floatParamT a floating point type, convenience to avoid double-float cofusion.
     449              :  *
     450              :  * \test Verify scaling and normalization of augment1SidedPSD \ref tests_sigproc_psdUtils_augment1SidedPSD "[test doc]"
     451              :  */
     452              : template <typename floatT, typename floatParamT>
     453         2049 : int normPSD( std::vector<floatT> &psd, ///< [in.out] the PSD to normalize, will be altered.
     454              :              std::vector<floatT> &f,   ///< [in] the frequency points for the PSD
     455              :              floatParamT normT,        ///< [in] the desired total variance (or integral) of the PSD.
     456              :              floatT fmin = std::numeric_limits<floatT>::min(), ///< [in] [optiona] the minimum frequency of the range
     457              :                                                                ///< over which to normalize.
     458              :              floatT fmax = std::numeric_limits<floatT>::max()  ///< [in] [optiona] the maximum frequency of the range
     459              :                                                                ///< over which to normalize.
     460              : )
     461              : {
     462         2049 :     floatT norm = normT;
     463              : 
     464         2049 :     floatT s = 0; // accumulate
     465              : 
     466              :     // Check if inside-the-loop branch is needed
     467         2049 :     if( fmin != std::numeric_limits<floatT>::min() || fmax != std::numeric_limits<floatT>::max() )
     468              :     {
     469       137089 :         for( size_t i = 0; i < psd.size(); ++i )
     470              :         {
     471       135041 :             if( fabs( f[i] ) < fmin || fabs( f[i] ) > fmax )
     472            0 :                 continue;
     473       135041 :             s += psd[i];
     474              :         }
     475              :     }
     476              :     else
     477              :     {
     478            6 :         for( size_t i = 0; i < psd.size(); ++i )
     479              :         {
     480            5 :             s += psd[i];
     481              :         }
     482              :     }
     483              : 
     484         2049 :     s *= ( f[1] - f[0] );
     485              : 
     486       137095 :     for( size_t i = 0; i < psd.size(); ++i )
     487       135046 :         psd[i] *= norm / s;
     488              : 
     489         2049 :     return 0;
     490              : }
     491              : 
     492              : /// Normalize a 2-D PSD to have a given variance
     493              : /** A frequency range can be specified for calculating the norm, otherwise the entire PSD is used.  The entire PSD is
     494              :  * normalized regardless.
     495              :  *
     496              :  * \tparam floatT the floating point type of the PSD.
     497              :  * \tparam floatParamT a floating point type, convenience to avoid double-float cofusion.
     498              :  *
     499              :  * \test Verify scaling and normalization of augment1SidedPSD \ref tests_sigproc_psdUtils_augment1SidedPSD "[test doc]"
     500              :  */
     501              : template <typename floatT, typename floatParamT>
     502              : floatT
     503            4 : normPSD( Eigen::Array<floatT, Eigen::Dynamic, Eigen::Dynamic> &psd, ///< [in.out] the PSD to normalize, will be altered.
     504              :          Eigen::Array<floatT, Eigen::Dynamic, Eigen::Dynamic> &k,   ///< [in] the frequency grid for psd.
     505              :          floatParamT normT, ///< [in] the desired total variance (or integral) of the PSD.
     506              :          floatT kmin = std::numeric_limits<floatT>::min(), ///< [in] [optiona] the minimum frequency of the range over
     507              :                                                            ///< which to normalize.
     508              :          floatT kmax = std::numeric_limits<floatT>::max()  ///< [in] [optiona] the maximum frequency of the range over
     509              :                                                            ///< which to normalize.
     510              : )
     511              : {
     512            4 :     floatT norm = normT;
     513              : 
     514              :     floatT dk1, dk2;
     515              : 
     516            4 :     if( k.rows() > 1 )
     517            4 :         dk1 = k( 1, 0 ) - k( 0, 0 );
     518              :     else
     519            0 :         dk1 = 1;
     520              : 
     521            4 :     if( k.cols() > 1 )
     522            4 :         dk2 = k( 0, 1 ) - k( 0, 0 );
     523              :     else
     524            0 :         dk2 = 1;
     525              : 
     526            4 :     floatT s = 0;
     527              : 
     528              :     // Check if inside-the-loop branch is needed
     529            4 :     if( kmin != std::numeric_limits<floatT>::min() || kmax != std::numeric_limits<floatT>::max() )
     530              :     {
     531            0 :         for( int c = 0; c < psd.cols(); ++c )
     532              :         {
     533            0 :             for( int r = 0; r < psd.rows(); ++r )
     534              :             {
     535            0 :                 if( fabs( k( r, c ) ) < kmin || fabs( k( r, c ) ) > kmax )
     536            0 :                     continue;
     537            0 :                 s += psd( r, c );
     538              :             }
     539              :         }
     540              :     }
     541              :     else
     542              :     {
     543          196 :         for( int c = 0; c < psd.cols(); ++c )
     544              :         {
     545        10432 :             for( int r = 0; r < psd.rows(); ++r )
     546              :             {
     547        10240 :                 s += psd( r, c );
     548              :             }
     549              :         }
     550              :     }
     551              : 
     552            4 :     s *= dk1 * dk2;
     553              : 
     554          196 :     for( int c = 0; c < psd.cols(); ++c )
     555              :     {
     556        10432 :         for( int r = 0; r < psd.rows(); ++r )
     557              :         {
     558        10240 :             psd( r, c ) *= norm / s;
     559              :         }
     560              :     }
     561              : 
     562            4 :     return 0;
     563              : }
     564              : 
     565              : /// Generates a @f$ 1/|f|^\alpha @f$ power spectrum
     566              : /**
     567              :  * Populates an Eigen array  with
     568              :  * \f[
     569              :  *  P(|f| = 0) = 0
     570              :  * \f]
     571              :  * \f[
     572              :  *  P(|f| > 0) = \frac{\beta}{|f|^{\alpha}}
     573              :  * \f]
     574              :  *
     575              :  *
     576              :  * \param [out] psd is the array to populate
     577              :  * \param [in] freq is a frequency grid, must be the same logical size as psd
     578              :  * \param [in] alpha is the power law exponent, by convention @f$ alpha > 0 @f$.
     579              :  * \param [in] beta [optional is a normalization constant to multiply the raw spectrum by.  If beta==-1 (default) then
     580              :  *                           the PSD is normalized using \ref oneoverf_norm.
     581              :  *
     582              :  * \tparam eigenArrp is the Eigen-like array type of the psd
     583              :  * \tparam eigenArrf is the Eigen-like array type of the frequency grid
     584              :  */
     585              : template <typename eigenArrp, typename eigenArrf>
     586              : void oneoverf_psd( eigenArrp &psd,
     587              :                    eigenArrf &freq,
     588              :                    typename eigenArrp::Scalar alpha,
     589              :                    typename eigenArrp::Scalar beta = -1 )
     590              : {
     591              :     typedef typename eigenArrp::Scalar Scalar;
     592              : 
     593              :     typename eigenArrp::Index dim_1, dim_2;
     594              :     Scalar f_x, f_y, p;
     595              : 
     596              :     dim_1 = psd.rows();
     597              :     dim_2 = psd.cols();
     598              : 
     599              :     if( beta == -1 )
     600              :     {
     601              :         Scalar fmin;
     602              :         Scalar fmax;
     603              : 
     604              :         fmax = freq.abs().maxCoeff();
     605              : 
     606              :         // Find minimum non-zero Coeff.
     607              :         fmin = ( freq.abs() > 0 ).select( freq.abs(), freq.abs() + fmax ).minCoeff();
     608              : 
     609              :         beta = oneoverf_norm( fmin, fmax, alpha );
     610              :     }
     611              : 
     612              :     for( int ii = 0; ii < dim_1; ++ii )
     613              :     {
     614              :         for( int jj = 0; jj < dim_2; ++jj )
     615              :         {
     616              :             if( freq( ii, jj ) == 0 )
     617              :             {
     618              :                 p = 0;
     619              :             }
     620              :             else
     621              :             {
     622              :                 p = beta / std::pow( std::abs( freq( ii, jj ) ), alpha );
     623              :             }
     624              :             psd( ii, jj ) = p;
     625              :         }
     626              :     }
     627              : }
     628              : 
     629              : /// Generate a 1-D von Karman power spectrum
     630              : /**
     631              :  * Populates an Eigen array  with
     632              :  *
     633              :  * \f[
     634              :  *  P(f) = \frac{\beta}{ (f^2 + (1/T_0)^2)^{\alpha/2}} e^{ - f^2 t_0^2}
     635              :  * \f]
     636              :  *
     637              :  * If you set \f$ T_0 \le 0 \f$ and \f$ t_0 = 0\f$ this reverts to a simple \f$ 1/f^\alpha \f$ law (i.e.
     638              :  * it treats this as infinite outer scale and inner scale).
     639              :  *
     640              :  * \returns error_t::noerror on success
     641              :  *
     642              :  * \tparam floatT a floating point
     643              :  */
     644              : template <typename floatT,
     645              :           typename floatfT,
     646              :           typename alphaT,
     647              :           typename T0T = double,
     648              :           typename t0T = double,
     649              :           typename betaT = double>
     650              : mx::error_t vonKarmanPSD( std::vector<floatT> &psd, ///< [out] the PSD vector, will be resized.
     651              :                           std::vector<floatfT> &f,  ///< [in] the frequency vector
     652              :                           alphaT alpha,             ///< [in] the exponent, by convention @f$ alpha > 0 @f$.
     653              :                           T0T T0 = 0,               ///< [in] the outer scale, default is 0 (not used).
     654              :                           t0T t0 = 0,               ///< [in] the inner scale, default is 0 (not used).
     655              :                           betaT beta = 1            ///< [in] the scaling constant, default is 1
     656              : )
     657              : {
     658              : 
     659              :     floatT T02;
     660              :     if( T0 > 0 )
     661              :     {
     662              :         T02 = 1.0 / ( T0 * T0 );
     663              :     }
     664              :     else
     665              :     {
     666              :         T02 = 0;
     667              :     }
     668              : 
     669              :     floatT sqrt_alpha = 0.5 * alpha;
     670              : 
     671              :     floatT _beta;
     672              :     if( beta <= 0 )
     673              :     {
     674              :         _beta = 1;
     675              :     }
     676              :     else
     677              :     {
     678              :         _beta = beta;
     679              :     }
     680              : 
     681              :     psd.resize( f.size() );
     682              : 
     683              :     if( t0 > 0 )
     684              :     {
     685              :         floatT _t0 = static_cast<floatT>( t0 );
     686              :         for( size_t i = 0; i < f.size(); ++i )
     687              :         {
     688              :             psd[i] = _beta / pow( pow( f[i], 2 ) + T02, sqrt_alpha ) * exp( -1 * pow( f[i] * _t0, 2 ) );
     689              :         }
     690              :     }
     691              :     else
     692              :     {
     693              :         for( size_t i = 0; i < f.size(); ++i )
     694              :         {
     695              :             psd[i] = _beta / pow( pow( f[i], 2 ) + T02, sqrt_alpha );
     696              :         }
     697              :     }
     698              : 
     699              :     return mx::error_t::noerror;
     700              : }
     701              : 
     702              : /// Generate a 1-D "knee" PSD
     703              : /**
     704              :  * Populates an Eigen array  with
     705              :  *
     706              :  * \f[
     707              :  *  P(f) = \frac{\beta}{ 1 + (f/f_n)^{\alpha}}
     708              :  * \f]
     709              :  *
     710              :  * If you set \f$ T_0 \le 0 \f$ and \f$ t_0 = 0\f$ this reverts to a simple \f$ 1/f^\alpha \f$ law (i.e.
     711              :  * it treats this as infinite outer scale and inner scale).
     712              :  *
     713              :  * \tparam floatT a floating point
     714              :  */
     715              : template <typename floatT>
     716              : int kneePSD( std::vector<floatT> &psd, ///< [out] the PSD vector, will be resized.
     717              :              std::vector<floatT> &f,   ///< [in] the frequency vector
     718              :              floatT beta,              ///< [in] the scaling constant
     719              :              floatT fn,                ///< [in] the knee frequency
     720              :              floatT alpha              ///< [in] the exponent, by convention @f$ alpha > 0 @f$.
     721              : )
     722              : {
     723              : 
     724              :     psd.resize( f.size() );
     725              : 
     726              :     for( int i = 0; i < f.size(); ++i )
     727              :     {
     728              :         floatT p = beta / ( 1 + pow( f[i] / fn, alpha ) );
     729              :         psd[i] = p;
     730              :     }
     731              : 
     732              :     return 0;
     733              : }
     734              : 
     735              : /// Generates a von Karman power spectrum
     736              : /**
     737              :  * Populates an Eigen array  with
     738              :  *
     739              :  * \f[
     740              :  *  P(k) = \frac{\beta}{ (k^2 + (1/L_0)^2)^{\alpha/2}} e^{ - k^2 l_0^2}
     741              :  * \f]
     742              :  *
     743              :  * If you set \f$ L_0 \le 0 \f$ and \f$ l_0 = 0\f$ this reverts to a simple \f$ 1/f^\alpha \f$ law (i.e.
     744              :  * it treats this as infinite outer scale and inner scale).
     745              :  *
     746              :  * \param [out] psd is the array to populate, allocated.
     747              :  * \param [in] freq is a frequency grid, must be the same logical size as psd
     748              :  * \param [in] alpha is the power law exponent, by convention @f$ alpha > 0 @f$.
     749              :  * \param [in] L0 [optional] is the outer scale.
     750              :  * \param [in] l0 [optional] is the inner scale.
     751              :  * \param [in] beta [optional] is a normalization constant to multiply the raw spectrum by.  If beta==-1 (default)
     752              :  * then the PSD is normalized using \ref oneoverf_norm.
     753              :  *
     754              :  * \tparam eigenArrp is the Eigen array type of the psd
     755              :  * \tparam eigenArrf is the Eigen array type of the frequency grid
     756              :  */
     757              : template <typename eigenArrp, typename eigenArrf, typename alphaT, typename L0T, typename l0T, typename betaT>
     758              : void vonKarmanPSD( eigenArrp &psd, eigenArrf &freq, alphaT alpha, L0T L0 = 0, l0T l0 = 0, betaT beta = -1 )
     759              : {
     760              :     typedef typename eigenArrp::Scalar Scalar;
     761              : 
     762              :     typename eigenArrp::Index dim_1, dim_2;
     763              :     Scalar p;
     764              : 
     765              :     dim_1 = psd.rows();
     766              :     dim_2 = psd.cols();
     767              : 
     768              :     Scalar _beta;
     769              : 
     770              :     if( beta == -1 )
     771              :     {
     772              :         Scalar fmin;
     773              :         Scalar fmax;
     774              : 
     775              :         fmax = freq.abs().maxCoeff();
     776              : 
     777              :         // Find minimum non-zero Coeff.
     778              :         fmin = ( freq.abs() > 0 ).select( freq.abs(), freq.abs() + fmax ).minCoeff();
     779              : 
     780              :         _beta = beta = oneoverf_norm( fmin, fmax, static_cast<Scalar>( alpha ) );
     781              :     }
     782              :     else
     783              :         _beta = static_cast<Scalar>( beta );
     784              : 
     785              :     Scalar L02;
     786              :     if( L0 > 0 )
     787              :         L02 = 1.0 / ( L0 * L0 );
     788              :     else
     789              :         L02 = 0;
     790              : 
     791              :     Scalar sqrt_alpha = 0.5 * alpha; // std::sqrt(alpha);
     792              : 
     793              :     for( int ii = 0; ii < dim_1; ++ii )
     794              :     {
     795              :         for( int jj = 0; jj < dim_2; ++jj )
     796              :         {
     797              :             if( freq( ii, jj ) == 0 && L02 == 0 )
     798              :             {
     799              :                 p = 0;
     800              :             }
     801              :             else
     802              :             {
     803              :                 p = _beta / pow( pow( freq( ii, jj ), 2 ) + L02, sqrt_alpha );
     804              :                 if( l0 > 0 )
     805              :                     p *= exp( -1 * pow( freq( ii, jj ) * static_cast<Scalar>( l0 ), 2 ) );
     806              :             }
     807              :             psd( ii, jj ) = p;
     808              :         }
     809              :     }
     810              : }
     811              : 
     812              : /// Augment a 1-sided PSD to standard 2-sided FFT form.
     813              : /** Allocates psdTwoSided to hold a flipped copy of psdOneSided.
     814              :  * Default assumes that psdOneSided[0] corresponds to 0 frequency,
     815              :  * but this can be changed by setting zeroFreq to a non-zero value.
     816              :  * In this case psdTwoSided[0] is set to 0, and the augmented psd
     817              :  * is shifted by 1.
     818              :  *
     819              :  * To illustrate, the bins are re-ordered as:
     820              :  * \verbatim
     821              :  * {1,2,3,4,5} --> {0,1,2,3,4,5,-4,-3,-2,-1}
     822              :  * \endverbatim
     823              :  *
     824              :  * The output is scaled so that the total power remains the same.  The 0-freq and
     825              :  * Nyquist freq are not scaled.
     826              :  *
     827              :  *
     828              :  * Entries in psdOneSided are cast to the value_type of psdTwoSided,
     829              :  * for instance to allow for conversion to complex type.
     830              :  *
     831              :  * \test Verify scaling and normalization of augment1SidedPSD \ref tests_sigproc_psdUtils_augment1SidedPSD "[test doc]"
     832              :  */
     833              : template <typename vectorTout, typename vectorTin>
     834            3 : void augment1SidedPSD(
     835              :     vectorTout &psdTwoSided, ///< [out] on return contains the FFT storage order copy of psdOneSided.
     836              :     vectorTin &psdOneSided,  ///< [in] the one-sided PSD to augment
     837              :     bool addZeroFreq =
     838              :         false, ///< [in] [optional] set to true if psdOneSided does not contain a zero frequency component.
     839              :     typename vectorTin::value_type scale = 0.5 ///< [in] [optional] value to scale the input by when copying to the
     840              :                                                ///< output.  The default 0.5 re-normalizes for a 2-sided PSD.
     841              : )
     842              : {
     843              :     typedef typename vectorTout::value_type outT;
     844              : 
     845            3 :     bool needZero = 1;
     846              : 
     847              :     size_t N;
     848              : 
     849            3 :     if( addZeroFreq == 0 )
     850              :     {
     851            3 :         needZero = 0;
     852            3 :         N = 2 * psdOneSided.size() - 2;
     853              :     }
     854              :     else
     855              :     {
     856            0 :         N = 2 * psdOneSided.size();
     857              :     }
     858              : 
     859            3 :     psdTwoSided.resize( N );
     860              : 
     861              :     // First set the 0-freq point
     862            3 :     if( needZero )
     863              :     {
     864            0 :         psdTwoSided[0] = outT( 0.0 );
     865              :     }
     866              :     else
     867              :     {
     868            3 :         psdTwoSided[0] = outT( psdOneSided[0] );
     869              :     }
     870              : 
     871              :     // Now set all the rest.
     872              :     unsigned long i;
     873         3076 :     for( i = 0; i < psdOneSided.size() - 1 - ( 1 - needZero ); ++i )
     874              :     {
     875         3073 :         psdTwoSided[i + 1] = outT( psdOneSided[i + ( 1 - needZero )] * scale );
     876         3073 :         psdTwoSided[i + psdOneSided.size() + needZero] = outT( psdOneSided[psdOneSided.size() - 2 - i] * scale );
     877              :     }
     878            3 :     psdTwoSided[i + 1] = outT( psdOneSided[i + ( 1 - needZero )] );
     879            3 : }
     880              : 
     881              : /// Augment a 1-sided frequency scale to standard FFT form.
     882              : /** Allocates freqTwoSided to hold a flipped copy of freqOneSided.
     883              :  * If freqOneSided[0] is not 0, freqTwoSided[0] is set to 0, and the augmented
     884              :  * frequency scale is shifted by 1.
     885              :  *
     886              :  * Example:
     887              :  *
     888              :  * {1,2,3,4,5} --> {0,1,2,3,4,5,-4,-3,-2,-1}
     889              :  *
     890              :  * \test Verify scaling and normalization of augment1SidedPSD \ref tests_sigproc_psdUtils_augment1SidedPSD "[test doc]"
     891              :  */
     892              : template <typename T>
     893            5 : void augment1SidedPSDFreq(
     894              :     std::vector<T> &freqTwoSided, ///< [out] on return contains the FFT storage order copy of freqOneSided.
     895              :     std::vector<T> &freqOneSided  ///< [in] the one-sided frequency scale to augment
     896              : )
     897              : {
     898            5 :     int needZero = 1;
     899              : 
     900              :     size_t N;
     901              : 
     902            5 :     if( freqOneSided[0] != 0 )
     903              :     {
     904            0 :         N = 2 * freqOneSided.size();
     905              :     }
     906              :     else
     907              :     {
     908            5 :         needZero = 0;
     909            5 :         N = 2 * freqOneSided.size() - 2;
     910              :     }
     911              : 
     912            5 :     freqTwoSided.resize( N );
     913              : 
     914            5 :     if( needZero )
     915              :     {
     916            0 :         freqTwoSided[0] = 0.0;
     917              :     }
     918              :     else
     919              :     {
     920            5 :         freqTwoSided[0] = freqOneSided[0]; // 0
     921              :     }
     922              : 
     923              :     int i;
     924         3140 :     for( i = 0; i < freqOneSided.size() - 1 - ( 1 - needZero ); ++i )
     925              :     {
     926         3135 :         freqTwoSided[i + 1] = freqOneSided[i + ( 1 - needZero )];
     927         3135 :         freqTwoSided[i + freqOneSided.size() + needZero] = -freqOneSided[freqOneSided.size() - 2 - i];
     928              :     }
     929            5 :     freqTwoSided[i + 1] = freqOneSided[i + ( 1 - needZero )];
     930            5 : }
     931              : 
     932              : /// Rebin a PSD, including its frequency scale, to a larger frequency bin size (fewer bins)
     933              : /** The rebinning uses trapezoid integration within bins to ensure minimum signal loss.
     934              :  *
     935              :  * Maintains DFT sampling.  That is, if initial frequency grid is 0,0.1,0.2...
     936              :  * and the binSize is 1.0, the new grid will be 0,1,2 (as opposed to 0.5, 1.5, 2.5).
     937              :  *
     938              :  * This introduces a question of what to do with first half-bin, which includes 0.  It can be
     939              :  * integrated (binAtZero = true, the default).  This may cause inaccurate behavior if the value of the PSD when
     940              :  * f=0 is important (e.g. when analyzing correlated noise), so setting binAtZero=false causes the f=0 value to be
     941              :  * copied (using the nearest neighbor if no f=0 point is in the input.
     942              :  *
     943              :  * The last half bin is always integrated.
     944              :  *
     945              :  * The output is variance normalized to match the input variance.
     946              :  *
     947              :  * \tparam realT  the real floating point type
     948              :  */
     949              : template <typename realT>
     950              : int rebin1SidedPSD( std::vector<realT> &binFreq, ///< [out] the binned frequency scale, resized.
     951              :                     std::vector<realT> &binPSD,  ///< [out] the binned PSD, resized.
     952              :                     std::vector<realT> &freq,    ///< [in] the frequency scale of the PSD to bin.
     953              :                     std::vector<realT> &PSD,     ///< [in] the PSD to bin.
     954              :                     realT binSize,               ///< [in] in same units as freq
     955              :                     bool binAtZero = true ///< [in] [optional] controls whether the zero point is binned or copied.
     956              : )
     957              : {
     958              :     binFreq.clear();
     959              :     binPSD.clear();
     960              : 
     961              :     realT sumPSD = 0;
     962              :     realT startFreq = 0;
     963              :     realT sumFreq = 0;
     964              :     int nSum = 0;
     965              : 
     966              :     int i = 0;
     967              : 
     968              :     realT df = freq[1] - freq[0];
     969              : 
     970              :     // Now move to first bin
     971              :     while( freq[i] <= 0.5 * binSize + 0.5 * df )
     972              :     {
     973              :         sumPSD += PSD[i];
     974              :         ++nSum;
     975              :         ++i;
     976              :         if( i >= freq.size() )
     977              :             break;
     978              :     }
     979              : 
     980              :     if( !binAtZero )
     981              :     {
     982              :         binFreq.push_back( 0 );
     983              :         binPSD.push_back( PSD[0] );
     984              :     }
     985              :     else
     986              :     {
     987              :         binFreq.push_back( 0 );
     988              :         binPSD.push_back( sumPSD / nSum );
     989              :     }
     990              : 
     991              :     --i;
     992              :     startFreq = freq[i];
     993              :     nSum = 0;
     994              :     sumFreq = 0;
     995              :     sumPSD = 0;
     996              : 
     997              :     while( i < freq.size() )
     998              :     {
     999              :         realT sc = 0.5; // First one is multiplied by 1/2 for trapezoid rule.
    1000              :         while( freq[i] - startFreq + 0.5 * df < binSize )
    1001              :         {
    1002              :             sumFreq += freq[i];
    1003              :             sumPSD += sc * PSD[i];
    1004              :             sc = 1.0;
    1005              :             ++nSum;
    1006              : 
    1007              :             ++i;
    1008              :             if( i >= freq.size() - 1 )
    1009              :                 break; // break 1 element early so last point is mult by 0.5
    1010              :         }
    1011              : 
    1012              :         if( i < freq.size() )
    1013              :         {
    1014              :             sumFreq += freq[i];
    1015              :             sumPSD += 0.5 * PSD[i]; // last one is multiplied by 1/2 for trapezoid rule
    1016              :             ++nSum;
    1017              :             ++i;
    1018              :         }
    1019              : 
    1020              :         // Check if this is last point
    1021              :         if( i < freq.size() )
    1022              :         {
    1023              :             binFreq.push_back( sumFreq / nSum );
    1024              :         }
    1025              :         else
    1026              :         {
    1027              :             // last point frequencyis not averaged.
    1028              :             binFreq.push_back( freq[freq.size() - 1] );
    1029              :         }
    1030              : 
    1031              :         binPSD.push_back( sumPSD / ( nSum - 1 ) );
    1032              : 
    1033              :         sumFreq = 0;
    1034              :         sumPSD = 0;
    1035              :         nSum = 0;
    1036              :         if( i >= freq.size() )
    1037              :             break;
    1038              : 
    1039              :         --i; // Step back one, so averages are edge to edge.
    1040              : 
    1041              :         startFreq = freq[i];
    1042              :     }
    1043              : 
    1044              :     // Now normalize variance
    1045              :     realT var = psdVar( freq, PSD );
    1046              :     realT binv = psdVar( binFreq, binPSD );
    1047              : 
    1048              :     for( int i = 0; i < binFreq.size(); ++i )
    1049              :         binPSD[i] *= var / binv;
    1050              : 
    1051              :     return 0;
    1052              : }
    1053              : ///@}
    1054              : 
    1055              : } // namespace sigproc
    1056              : } // namespace mx
    1057              : 
    1058              : #endif // psdUtils_hpp
        

Generated by: LCOV version 2.0-1