mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
averagePeriodogram.hpp
Go to the documentation of this file.
1 /** \file averagePeriodogram.hpp
2  * \brief A class to manage calculation of periodograms from time series data.
3  *
4  * \author Jared R. Males (jaredmales@gmail.com)
5  *
6  * \ingroup signal_processing_files
7  *
8  */
9 
10 //***********************************************************************//
11 // Copyright 2020 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 averagePeriodogram_hpp
30 #define averagePeriodogram_hpp
31 
32 #include "psdUtils.hpp"
33 
34 
35 namespace mx
36 {
37 namespace sigproc
38 {
39 
40 /// Calculate the average periodogram of a time-series.
41 /**
42  * Implements the overlapped average periodogram, cf. pp 843 to 845 of \cite 10.5555.1795494. This produces
43  * the variance normalized 1-sided PSD estimate of the periodogram.
44  *
45  * Optionally includes a window (by default no window is used, which is equivalent to the rectangle window).
46  *
47  * Can also be used to calculate the unaveraged non-overlapped periodogram of the entire time series.
48  *
49  * Example:
50  \code
51  typedef float realT;
52 
53  mx::fftwEnvironment<realT> fftwEnv; //for efficient fft planning
54 
55  std::vector<realT> ts = getTimeSeries(); //Some function to populate ts.
56 
57  math::vectorMeanSub(ts); //The time series should be overall mean subtracted.
58 
59  realT dt = 0.1; //The sampling of ts is 0.1 seconds.
60 
61  averagePeriodogram<realT> avgPgram(2.0/dt, dt); //This sets the averaging length to 2 seconds (20 samples), with a 1 second overlap (set automatically)
62 
63  avgPgram.win(window::hann); //Set the Hann window
64 
65  std::vector<realT> pgram = avgPgram(ts); //Calculate the half-overlapped periodogram estimate for ts.
66 
67  //Now output the periodogram estimate. This will give two columns, the first frequency, the second power at that frequency.
68  for(size_t n=0; n< avgPgram.size(); ++n)
69  {
70  std::cout << avgPgram[n] << " " << pgram[n] << "\n";
71  }
72 
73  \endcode
74  *
75  * \ingroup psds
76  */
77 template<typename realT>
79 {
80 
81 protected:
82 
83  /** \name Configuration Parameters
84  *
85  *@{
86  */
87 
88  size_t m_avgLen {0}; ///< The number of samples in each periodgoram estimate.
89  size_t m_overlap {0}; ///< The number of samples by which to overlap. This should almost always be 0.5*m_avgLen. Set 0 for the non-overlapped case.
90 
91  realT m_dt {1}; ///< The time sampling. Only used for normalization and calculation of the frequency scale.
92 
93  std::vector<realT> m_win; ///< The window function. By default this is empty, which is equivalent to setting it to the rectangular window.
94 
95  ///@}
96 
97  size_t m_size {0}; ///< The size of the periodogram vector, calculated as m_avgLen/2 + 1;
98 
99  int m_nOver {0}; ///< The number of overlapping segments. Calculated from m_avgLen and m_overlap;
100 
101  realT m_df {1}; ///< The frequency sampling. This is used only for normalization and frequency scale output.
102 
103 
104  math::fft::fftT< realT, std::complex<realT>, 1, 0> m_fft;
105 
106  realT * m_tsWork {nullptr};
107  size_t m_tsWorkSize {0};
108 
109  std::complex<realT> * m_fftWork {nullptr};
110  size_t m_fftWorkSize {0};
111 
112 public:
113 
114  /// C'tor which sets up the optimum overlapped periodogram of the timeseries.
115  /** Sets m_overlap = 0.5*m_avgLen. If you desire the non-overlapped case use
116  * the alternate constructor:
117  * \code
118  averagePeriogram p( avgLen, 0, dt);
119  \endcode
120  */
121  explicit averagePeriodogram( size_t avgLen /**< [in] The length of averaging in samples.*/ );
122 
123  /// C'tor which sets up the optimum overlapped periodogram of the timeseries and sets the sampling.
124  /** Sets m_overlap = 0.5*m_avgLen. If you desire the non-overlapped case use
125  * the alternate constructor:
126  * \code
127  averagePeriogram p( avgLen, 0, dt);
128  \endcode
129  */
130  averagePeriodogram( size_t avgLen, ///< [in] The length of averaging in samples.
131  realT dt ///< [in] the sampling interval of the time-series
132  );
133 
134  /// C'tor setting up an arbitrary overlap.
135  /** Set olap to 0 for the unoverlapped case.
136  */
137  averagePeriodogram( size_t avgLen, ///< [in] The number of samples in each periodgoram estimate.
138  size_t olap, ///< [in] The number of samples by which to overlap. This should almost always be 0.5*m_avgLen. Set 0 for the non-overlapped case.
139  realT dt ///< [in] the sampling interval of the time-series
140  );
141 
142  /// D'tor, frees all working memory.
144 
145  /// Set the sampling interval of the time-series
146  /** This also sets the frequency scale of the output.
147  */
148  void dt( realT ndt);
149 
150  /// Get the sampling interval of the time-series
151  /** \returns m_dt
152  */
153  realT dt();
154 
155  /// Get the frequency interval of the periodogram
156  /** \returns m_df
157  */
158  realT df();
159 
160  /// Resize the periodogram working memory, setting up the 1/2-overlapped optimum case.
161  /** This sets the overlap to 0.5*avgLen.
162  *
163  * Also performs fft planning.
164  *
165  * \returns 0 on success
166  * \returns -1 on error
167  */
168  int resize( size_t avgLen /**< [in] The number of samples in each periodgoram estimate. */);
169 
170  /// Resize the periodogram working memory.
171  /** Also performs fft planning.
172  *
173  * \returns 0 on success
174  * \returns -1 on error
175  */
176  int resize( size_t avgLen, ///< [in] The number of samples in each periodgoram estimate.
177  size_t olap ///< [in] The number of samples by which to overlap. This should almost always be 0.5*m_avgLen. Set 0 for the non-overlapped case.
178  );
179 
180  /// Get a reference to the window vector.
181  /** This allows population of the window with any arbitrary function. You should call
182  * resizeWin() before using this to make sure that the vector has the correct length.
183  *
184  * \returns a reference to m_win.
185  */
186  std::vector<realT> & win();
187 
188  /// Set the window vector using a function.
189  /** This will resize m_win, and then call the function with m_win as the argument.
190  * For example to use the hann window defined \ref signal_windows1D:
191  * \code
192  avgPgram.win( window::hann );
193  \endcode
194  * which will set the Hann window.
195  */
196  void win( void(*winFunc)(std::vector<realT> &) /**<[in] pointer to a function which takes a pre-allocated vector and fills it in with a window function*/);
197 
198  /// Resize the window and, unless not desired, initialize to the rectangular window by setting all 1s.
199  /** If `setRect=false` then the window is not initialized.
200  */
201  void resizeWin( bool setRect=true /**< [in] [optional] if false, then the window is not initialized to 1 */);
202 
203  /// Calculate the periodogram for a time-series.
204  /**
205  *
206  */
207  void operator()( realT * pgram, ///< [out] a pre-allocated to size() array which will be populated with the periodogram of the time-series
208  const realT * ts, ///< [in] the time-series
209  size_t sz ///< [in] the length of the time-series array
210  );
211 
212 
213  /// Calculate the periodogram for a time-series.
214  /** \overload
215  *
216  */
217  void operator()( std::vector<realT> & pgram, ///< [out] a vector which will be allocated and populated with the periodogram of the time-series
218  const std::vector<realT> & ts ///< [in] the time-series
219  );
220 
221  /// Calculate the periodogram for a time-series.
222  /** \overload
223  *
224  * \returns the periodogram as a vector.
225  */
226  std::vector<realT> operator()(std::vector<realT> & ts /**< [in] the time-series*/ );
227 
228  /// Return the size of the periodogram.
229  /**
230  *
231  * \returns the size of the periodogram estimate for the current setup.
232  */
233  size_t size();
234 
235  /// Get the frequency at a given point in the periodogram.
236  /**
237  * \returns the frequency at point n in the periodogram.
238  */
239  realT operator[]( size_t n /**<[in] the point in the periodogram at which the frequency is desired*/ );
240 
241 };
242 
243 template<typename realT>
245 {
246  resize(avgLen, 0.5*avgLen);
247  dt(1);
248 }
249 
250 template<typename realT>
252  realT ndt
253  )
254 {
255  resize(avgLen, 0.5*avgLen);
256  dt(ndt);
257 }
258 
259 template<typename realT>
261  size_t olap,
262  realT ndt
263  )
264 {
265  resize(avgLen, olap);
266  dt(ndt);
267 }
268 
269 template<typename realT>
271 {
272  if(m_tsWork) fftw_free(m_tsWork);
273  if(m_fftWork) fftw_free(m_fftWork);
274 }
275 
276 template<typename realT>
278 {
279  m_dt = ndt;
280  m_df = 1.0/(m_avgLen*m_dt);
281 }
282 
283 template<typename realT>
285 {
286  return m_dt;
287 }
288 
289 template<typename realT>
291 {
292  return m_df;
293 }
294 
295 
296 template<typename realT>
298 {
299  return resize(avgLen, 0.5*avgLen);
300 }
301 
302 template<typename realT>
304  size_t olap
305  )
306 {
307  m_avgLen = avgLen;
308 
309  m_size = m_avgLen/2 + 1;
310 
311  m_df = 1.0/(m_avgLen*m_dt);
312 
313  m_overlap = olap;
314 
315  m_nOver = (m_avgLen-m_overlap);
316 
317  m_fft.plan(m_avgLen, MXFFT_FORWARD, false);
318 
319  if(m_tsWork) fftw_free(m_tsWork);
320  m_tsWork = math::fft::fftw_malloc<realT>( m_avgLen );
321 
322  if(m_fftWork) fftw_free(m_fftWork);
323 
324  m_fftWork = math::fft::fftw_malloc<std::complex<realT>>( (m_avgLen/2 + 1) );
325 
326  return 0;
327 }
328 
329 template<typename realT>
330 std::vector<realT> & averagePeriodogram<realT>::win()
331 {
332  return m_win;
333 }
334 
335 template<typename realT>
336 void averagePeriodogram<realT>::win( void(*winFunc)(std::vector<realT> &) )
337 {
338  resizeWin(false);
339  winFunc(m_win);
340 }
341 
342 template<typename realT>
344 {
345  if(setRect)
346  {
347  m_win.resize(m_avgLen, static_cast<realT>(1));
348  }
349  else
350  {
351  m_win.resize(m_avgLen);
352  }
353 }
354 
355 template<typename realT>
357  const realT * ts,
358  size_t sz
359  )
360 {
361  if( m_win.size() > 0 && m_win.size() != m_avgLen )
362  {
363  std::cerr << "averagePeriodogram: Window size not correct.\n";
364  }
365 
366  int Navg = sz/m_nOver;
367 
368  while(Navg*m_nOver + m_avgLen > sz) --Navg;
369 
370  if(Navg < 1) Navg = 1; //Always do at least 1!
371 
372  //mx::fftT< realT, std::complex<realT>, 1, 0> fft(m_avgLen);
373 
374  for(int i=0;i<Navg;++i)
375  {
376  if(m_win.size() == m_avgLen) //no if inside the for loop
377  {
378  for(size_t j=0;j<m_avgLen;++j)
379  {
380  m_tsWork[j] = ts[i*m_nOver + j] * m_win[j];
381  }
382  }
383  else
384  {
385  for(size_t j=0;j<m_avgLen;++j)
386  {
387  m_tsWork[j] = ts[i*m_nOver + j];
388  }
389  }
390 
391  m_fft( m_fftWork, m_tsWork);
392 
393  for(size_t j=0;j<m_size;++j) pgram[j] += norm(m_fftWork[j]);
394  }
395 
396  //This is what you'd do to normalize for dt=1
397  //for(size_t j=0;j<m_size;++j) pgram[j] /= (m_avgLen*Navg);
398 
399  //but we will just always normalize:
400 
401  realT pgramVar = psdVar1sided(m_df, pgram, m_size);
402 
403  realT tsVar = mx::math::vectorVariance(ts, sz);
404 
405  for(size_t j =0; j< m_size; ++j) pgram[j] *= tsVar/pgramVar;
406 
407 }
408 
409 template<typename realT>
410 void averagePeriodogram<realT>::operator()( std::vector<realT> & pgram,
411  const std::vector<realT> & ts
412  )
413 {
414  pgram.resize(m_size);
415  operator()( pgram.data(), ts.data(), ts.size());
416 
417 }
418 
419 template<typename realT>
420 std::vector<realT> averagePeriodogram<realT>::operator()(std::vector<realT> & ts)
421 {
422  std::vector<realT> pgram;
423  operator()(pgram, ts);
424  return pgram;
425 }
426 
427 template<typename realT>
429 {
430  return m_size;
431 }
432 
433 template<typename realT>
435 {
436  return n*m_df;
437 }
438 
439 
440 } //namespace sigproc
441 } //namespace mx
442 
443 #endif //averagePeriodogram_hpp
Calculate the average periodogram of a time-series.
~averagePeriodogram()
D'tor, frees all working memory.
int resize(size_t avgLen)
Resize the periodogram working memory, setting up the 1/2-overlapped optimum case.
realT df()
Get the frequency interval of the periodogram.
realT m_dt
The time sampling. Only used for normalization and calculation of the frequency scale.
size_t m_avgLen
The number of samples in each periodgoram estimate.
size_t m_size
The size of the periodogram vector, calculated as m_avgLen/2 + 1;.
void operator()(realT *pgram, const realT *ts, size_t sz)
Calculate the periodogram for a time-series.
size_t m_overlap
The number of samples by which to overlap. This should almost always be 0.5*m_avgLen....
std::vector< realT > m_win
The window function. By default this is empty, which is equivalent to setting it to the rectangular w...
void resizeWin(bool setRect=true)
Resize the window and, unless not desired, initialize to the rectangular window by setting all 1s.
averagePeriodogram(size_t avgLen)
C'tor which sets up the optimum overlapped periodogram of the timeseries.
size_t size()
Return the size of the periodogram.
realT operator[](size_t n)
Get the frequency at a given point in the periodogram.
int m_nOver
The number of overlapping segments. Calculated from m_avgLen and m_overlap;.
realT dt()
Get the sampling interval of the time-series.
std::vector< realT > & win()
Get a reference to the window vector.
realT m_df
The frequency sampling. This is used only for normalization and frequency scale output.
void fftw_free(realT *p)
Call to fftw_free.
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
valueT vectorVariance(const valueT *vec, size_t sz, valueT mean)
Calculate the variance of a vector relative to a supplied mean value.
The mxlib c++ namespace.
Definition: mxError.hpp:107
Tools for working with PSDs.