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