File Coverage

blib/lib/Math/FFT.pm
Criterion Covered Total %
statement 334 370 90.2
branch 77 164 46.9
condition 18 36 50.0
subroutine 31 31 100.0
pod 23 23 100.0
total 483 624 77.4


line stmt bran cond sub pod time code
1             package Math::FFT;
2              
3 3     3   42265 use strict;
  3         4  
  3         73  
4 3     3   9 use warnings;
  3         3  
  3         60  
5              
6 3     3   49 use 5.008;
  3         6  
7              
8 3     3   11 use vars qw(@ISA);
  3         3  
  3         8695  
9             require DynaLoader;
10              
11             @ISA = qw(DynaLoader);
12              
13             # Items to export into callers namespace by default. Note: do not export
14             # names by default without a very good reason. Use EXPORT_OK instead.
15             # Do not simply export all your public functions/methods/constants.
16              
17             our $VERSION = '1.33';
18              
19             bootstrap Math::FFT $VERSION;
20              
21             # Preloaded methods go here.
22              
23             sub new {
24 21     21 1 283388 my ($class, $data) = @_;
25 21 50       90 die 'Must call constructor with an array reference for the data'
26             unless ref($data) eq 'ARRAY';
27 21   100     66 $data->[0] ||= 0; # keep warnings happy
28 21         29 my $n = @$data;
29 21         48 my $nip = int(3 + sqrt($n));
30 21         50 my $nw = int(2 + 5*$n/4);
31 21         107 my $ip = pack("i$nip", ());
32 21         1533 my $w = pack("d$nw", ());
33 21         161 return bless {
34             type => '',
35             mean => '',
36             coeff => '',
37             n => $n,
38             data => $data,
39             ip => \$ip,
40             w => \$w,
41             }, $class;
42             }
43              
44             # clone method to copy the ip and w arrays for data of equal size
45             sub clone {
46 1     1 1 21255 my ($self, $data) = @_;
47 1 50       8 die 'Must call clone with an array reference for the data'
48             unless ref($data) eq 'ARRAY';
49 1   50     4 $data->[0] ||= 0; # keep warnings happy
50 1         2 my $n = @$data;
51 1 50       15 die "Cannot clone data of unequal sizes" unless $n == $self->{n};
52 1         2 my $class = ref($self);
53             return bless {
54             type => '',
55             coeff => '',
56             mean => '',
57             n => $self->{n},
58             data => $data,
59             ip => $self->{ip},
60             w => $self->{w},
61 1         12 }, $class;
62             }
63              
64             # Complex Discrete Fourier Transform
65             sub cdft {
66 4     4 1 30 my $self = shift;
67 4         16 my $n = $self->{n};
68 4 50       12 die "data size ($n) must be an integer power of 2" unless _check_n($n);
69 4         6 my $data = [ @{$self->{data}} ];
  4         3925  
70 4         11647 _cdft($n, 1, $data, $self->{ip}, $self->{w});
71 4         43 $self->{type} = 'cdft';
72 4         7 $self->{coeff} = $data;
73 4         18 return $data;
74             }
75              
76             # Inverse Complex Discrete Fourier Transform
77             sub invcdft {
78 4     4 1 353 my $self = shift;
79 4         6 my $data;
80 4         9 my $n = $self->{n};
81 4 50       13 if (my $arg = shift) {
82 0 0       0 if (ref($arg) ne 'ARRAY')
83             {
84 0         0 die 'Must pass an array reference to invcdft';
85             }
86 0 0       0 if ($n != @$arg)
87             {
88 0         0 die "Size of data set must be $n";
89             }
90 0         0 $data = [ @$arg ];
91             }
92             else {
93             die 'Must invert data created with cdft'
94 4 50       15 unless $self->{type} eq 'cdft';
95 4         6 $data = [ @{$self->{coeff}} ];
  4         3828  
96             }
97 4         11674 _cdft($n, -1, $data, $self->{ip}, $self->{w});
98 4         11993 $_ *= 2.0/$n for (@$data);
99 4         28 return $data;
100             }
101              
102             # Real Discrete Fourier Transform
103             sub rdft {
104 7     7 1 22 my $self = shift;
105 7         8 my $n = $self->{n};
106 7 50       20 if (!_check_n($n))
107             {
108 0         0 die "data size ($n) must be an integer power of 2";
109             }
110 7         7 my $data = [ @{$self->{data}} ];
  7         963  
111 7         4270 _rdft($n, 1, $data, $self->{ip}, $self->{w});
112 7         22 $self->{type} = 'rdft';
113 7         7 $self->{coeff} = $data;
114 7         21 return $data;
115             }
116              
117             # Inverse Real Discrete Fourier Transform
118             sub invrdft {
119 3     3 1 535 my $self = shift;
120 3         4 my $data;
121 3         7 my $n = $self->{n};
122 3 50       8 if (my $arg = shift) {
123 0 0       0 die 'Must pass an array reference to invrdft'
124             unless ref($arg) eq 'ARRAY';
125 0 0       0 die "Size of data set must be $n"
126             unless $n == @$arg;
127 0         0 $data = [ @$arg ];
128             }
129             else {
130             die 'Must invert data created with rdft'
131 3 50       10 unless $self->{type} eq 'rdft';
132 3         3 $data = [ @{$self->{coeff}} ];
  3         802  
133             }
134 3         3909 _rdft($n, -1, $data, $self->{ip}, $self->{w});
135 3         3779 $_ *= 2.0/$n for (@$data);
136 3         10 return $data;
137             }
138              
139             # Discrete Cosine Transform
140             sub ddct {
141 2     2 1 16 my $self = shift;
142 2         4 my $n = $self->{n};
143 2 50       7 die "data size ($n) must be an integer power of 2" unless _check_n($n);
144 2         4 my $data = [ @{$self->{data}} ];
  2         938  
145 2         5054 _ddct($n, -1, $data, $self->{ip}, $self->{w});
146 2         5 $self->{type} = 'ddct';
147 2         4 $self->{coeff} = $data;
148 2         7 return $data;
149             }
150              
151             # Inverse Discrete Cosine Transform
152             sub invddct {
153 2     2 1 381 my $self = shift;
154 2         3 my $data;
155 2         4 my $n = $self->{n};
156 2 50       5 if (my $arg = shift) {
157 0 0       0 die 'Must pass an array reference to invddct'
158             unless ref($arg) eq 'ARRAY';
159 0 0       0 die "Size of data set must be $n"
160             unless $n == @$arg;
161 0         0 $data = [ @$arg ];
162             }
163             else {
164             die 'Must invert data created with ddct'
165 2 50       7 unless $self->{type} eq 'ddct';
166 2         3 $data = [ @{$self->{coeff}} ];
  2         781  
167             }
168 2         5 $data->[0] *= 0.5;
169 2         4209 _ddct($n, 1, $data, $self->{ip}, $self->{w});
170 2         3797 $_ *= 2.0/$n for (@$data);
171 2         6 return $data;
172             }
173              
174             # Discrete Sine Transform
175             sub ddst {
176 2     2 1 14 my $self = shift;
177 2         4 my $n = $self->{n};
178 2 50       5 die "data size ($n) must be an integer power of 2" unless _check_n($n);
179 2         3 my $data = [ @{$self->{data}} ];
  2         924  
180 2         5094 _ddst($n, -1, $data, $self->{ip}, $self->{w});
181 2         6 $self->{type} = 'ddst';
182 2         4 $self->{coeff} = $data;
183 2         6 return $data;
184             }
185              
186             # Inverse Discrete Sine Transform
187             sub invddst {
188 2     2 1 314 my $self = shift;
189 2         3 my $data;
190 2         3 my $n = $self->{n};
191 2 50       6 if (my $arg = shift) {
192 0 0       0 die 'Must pass an array reference to invddst'
193             unless ref($arg) eq 'ARRAY';
194 0 0       0 die "Size of data set must be $n"
195             unless $n == @$arg;
196 0         0 $data = [ @$arg ];
197             }
198             else {
199             die 'Must invert data created with ddst'
200 2 50       7 unless $self->{type} eq 'ddst';
201 2         3 $data = [ @{$self->{coeff}} ];
  2         786  
202             }
203 2         3 $data->[0] *= 0.5;
204 2         4229 _ddst($n, 1, $data, $self->{ip}, $self->{w});
205 2         3902 $_ *= 2.0/$n for (@$data);
206 2         7 return $data;
207             }
208              
209             # Cosine Transform of RDFT (Real Symmetric DFT)
210             sub dfct {
211 2     2 1 15 my $self = shift;
212 2         3 my $np1 = $self->{n};
213 2         5 my $n = $np1 - 1;
214 2 50       9 die "data size ($n) must be an integer power of 2" unless _check_n($n);
215 2         6 my $nt = int(2 + $n/2);
216 2         4 my $t = [];
217 2         4 my $data = [ @{$self->{data}} ];
  2         954  
218 2         6798 pdfct($nt, $n, $data, $t, $self->{ip}, $self->{w});
219 2         8 $self->{type} = 'dfct';
220 2         3 $self->{coeff} = $data;
221 2         210 return $data;
222             }
223              
224             # Inverse Cosine Transform of RDFT (Real Symmetric DFT)
225             sub invdfct {
226 2     2 1 484 my $self = shift;
227 2         2 my $data;
228 2         3 my $np1 = $self->{n};
229 2         6 my $n = $np1 - 1;
230 2 50       6 if (my $arg = shift) {
231 0 0       0 die 'Must pass an array reference to invdfct'
232             unless ref($arg) eq 'ARRAY';
233 0 0       0 die "Size of data set must be $n"
234             unless $np1 == @$data;
235 0         0 $data = [ @$arg ];
236             }
237             else {
238             die 'Must invert data created with dfct'
239 2 50       6 unless $self->{type} eq 'dfct';
240 2         2 $data = [ @{$self->{coeff}} ];
  2         777  
241             }
242 2         8 my $nt = int(2 + $n/2);
243 2         1 my $t = [];
244 2         4 $data->[0] *= 0.5;
245 2         3 $data->[$n] *= 0.5;
246 2         6261 pdfct($nt, $n, $data, $t, $self->{ip}, $self->{w});
247 2         7 $data->[0] *= 0.5;
248 2         5 $data->[$n] *= 0.5;
249 2         3865 $_ *= 2.0/$n for (@$data);
250 2         277 return $data;
251             }
252              
253             # Sine Transform of RDFT (Real Anti-symmetric DFT)
254             sub dfst {
255 2     2 1 15 my $self = shift;
256 2         4 my $n = $self->{n};
257 2 50       8 die "data size ($n) must be an integer power of 2" unless _check_n($n);
258 2         6 my $data = [ @{$self->{data}} ];
  2         838  
259 2         7 my $nt = int(2 + $n/2);
260 2         3 my $t = [];
261 2         6416 pdfst($nt, $n, $data, $t, $self->{ip}, $self->{w});
262 2         6 $self->{type} = 'dfst';
263 2         4 $self->{coeff} = $data;
264 2         194 return $data;
265             }
266              
267             # Inverse Sine Transform of RDFT (Real Anti-symmetric DFT)
268             sub invdfst {
269 2     2 1 344 my $self = shift;
270 2         3 my $n = $self->{n};
271 2         2 my $data;
272 2 50       7 if (my $arg = shift) {
273 0 0       0 die 'Must pass an array reference to invdfst'
274             unless ref($arg) eq 'ARRAY';
275 0 0       0 die "Size of data set must be $n"
276             unless $n == @$arg;
277 0         0 $data = [ @$arg ];
278             }
279             else {
280             die 'Must invert data created with dfst'
281 2 50       7 unless $self->{type} eq 'dfst';
282 2         2 $data = [ @{$self->{coeff}} ];
  2         827  
283             }
284 2         7 my $nt = int(2 + $n/2);
285 2         2 my $t = [];
286 2         5952 pdfst($nt, $n, $data, $t, $self->{ip}, $self->{w});
287 2         3868 $_ *= 2.0/$n for (@$data);
288 2         204 return $data;
289             }
290              
291             # check if $n is a power of 2
292             sub _check_n {
293 31     31   42 my ($n) = @_;
294              
295 31   33     262 return scalar($n == int($n) and $n > 0 and (! ($n & ($n-1))));
296             }
297              
298             sub correl {
299 4     4 1 14 my ($self, $other) = @_;
300 4         11 my $n = $self->{n};
301             my $d1 = $self->{type} ?
302 3         4 ($self->{type} eq 'rdft' ? [ @{$self->{coeff}} ] :
303             die 'correl must involve a real function' ) :
304 4 50 50     13 $self->rdft && [ @{$self->{coeff}} ];
    100          
305 4         5 my $d2 = [];
306 4 100       11 if (ref($other) eq 'Math::FFT') {
    50          
307             $d2 = $other->{type} ?
308 1         11 ($other->{type} eq 'rdft' ? [ @{$other->{coeff}}] :
309             die 'correl must involve a real function' ) :
310 2 50 50     7 $other->rdft && [ @{$other->{coeff}}];
    100          
311             }
312             elsif (ref($other) eq 'ARRAY') {
313 2         4 $d2 = [ @$other ];
314 2         11 _rdft($n, 1, $d2, $self->{ip}, $self->{w});
315             }
316             else {
317 0         0 die 'Must call correl with either a Math::FFT object or an array ref';
318             }
319 4         5 my $corr = [];
320 4         40 _correl($n, $corr, $d1, $d2, $self->{ip}, $self->{w});
321 4         12 return $corr;
322             }
323              
324             sub convlv {
325 2     2 1 429 my ($self, $r) = @_;
326 2 50       5 die 'Must call convlv with an array reference for the response data'
327             unless ref($r) eq 'ARRAY';
328 2         4 my $respn = [ @$r ];
329 2         3 my $m = @$respn;
330 2 50       4 die 'size of response data must be an odd integer' unless $m % 2 == 1;
331 2         3 my $n = $self->{n};
332             my $d1 = $self->{type} ?
333 2         4 ($self->{type} eq 'rdft' ? [ @{$self->{coeff}} ] :
334             die 'correl must involve a real function' ) :
335 2 50 0     6 $self->rdft && [ @{$self->{coeff}} ];
    50          
336 2         7 for (my $i=1; $i<=($m-1)/2; $i++) {
337 8         18 $respn->[$n-$i] = $respn->[$m-$i];
338             }
339 2         7 for (my $i=($m+3)/2; $i<=$n-($m-1)/2; $i++) {
340 14         18 $respn->[$i-1] = 0.0;
341             }
342 2         2 my $convlv = [];
343 2         31 _convlv($n, $convlv, $d1, $respn, $self->{ip}, $self->{w});
344 2         6 return $convlv;
345             }
346              
347             sub deconvlv {
348 1     1 1 5 my ($self, $r) = @_;
349 1 50       3 die 'Must call deconvlv with an array reference for the response data'
350             unless ref($r) eq 'ARRAY';
351 1         2 my $respn = [ @$r ];
352 1         1 my $m = @$respn;
353 1 50       3 die 'size of response data must be an odd integer' unless $m % 2 == 1;
354 1         1 my $n = $self->{n};
355             my $d1 = $self->{type} ?
356 0         0 ($self->{type} eq 'rdft' ? [ @{$self->{coeff}} ] :
357             die 'correl must involve a real function' ) :
358 1 0 50     4 $self->rdft && [ @{$self->{coeff}} ];
    50          
359 1         4 for (my $i=1; $i<=($m-1)/2; $i++) {
360 4         7 $respn->[$n-$i] = $respn->[$m-$i];
361             }
362 1         5 for (my $i=($m+3)/2; $i<=$n-($m-1)/2; $i++) {
363 7         9 $respn->[$i-1] = 0.0;
364             }
365 1         2 my $convlv = [];
366 1 50       13 if (_deconvlv($n, $convlv, $d1, $respn, $self->{ip}, $self->{w}) != 0) {
367 0         0 die "Singularity encountered for response in deconvlv";
368             }
369 1         3 return $convlv;
370             }
371              
372             {
373              
374             my $PI2 = 2 * 4.0 * atan2(1,1);
375             sub spctrm {
376 12     12 1 2127 my ($self, %args) = @_;
377 12         18 my %accept = map {$_ => 1} qw(window segments number overlap);
  48         60  
378 12         27 for (keys %args) {
379 39 50       59 die "`$_' is not a valid argument to spctrm" if not $accept{$_};
380             }
381 12         13 my $win_fun = $args{window};
382 12 100 100     40 if ($win_fun and ref($win_fun) ne 'CODE') {
383 7         7 my %accept = map {$_ => 1} qw(hamm hann welch bartlett);
  28         33  
384             die "`$win_fun' is not a known window function in spctrm"
385 7 50       16 if not $accept{$win_fun};
386             }
387             die 'Please specify a value for "segments" in spctrm()'
388 12 50 66     36 if ($args{number} and ! $args{segments});
389 12         12 my $n = $self->{n};
390 12         10 my $d;
391 12         10 my $n2 = 0;
392 12         10 my $spctrm = [];
393 12         7 my $win_sub = do {
394             my $h = sub {
395 64     64   45 my ($j, $n) = @_;
396 64         68 return (1 - cos($PI2*$j/$n))/2;
397 12         37 };
398              
399             +{
400             'hamm' => $h,
401             'hann' => $h,
402             'welch' => sub {
403 64     64   41 my ($j, $n) = @_;
404 64         74 return 1 - 4*($j-$n/2)*($j-$n/2)/$n/$n;
405             },
406             'bartlett' => sub {
407 80     80   72 my ($j, $n) = @_;
408 80         87 return 1 - abs(2*($j-$n/2)/$n);
409             },
410             }
411 12         56 };
412 12 100 33     38 if (not $args{segments} or ($args{segments} == 1 and not $args{number})) {
      66        
413 2 50       4 die "data size ($n) must be an integer power of 2" unless _check_n($n);
414 2 100       6 if ($win_fun) {
415 1         1 $d = [ @{$self->{data}}];
  1         4  
416 1 50       3 $win_fun = $win_sub->{$win_fun} if ref($win_fun) ne 'CODE';
417 1         4 for (my $j=0; $j<$n; $j++) {
418 16         14 my $w = $win_fun->($j, $n);
419 16         11 $d->[$j] *= $w;
420 16         22 $n2 += $w * $w;
421             }
422 1         2 $n2 *= $n;
423 1         27 _spctrm($n, $spctrm, $d, $self->{ip}, $self->{w}, $n2, 1);
424             }
425             else {
426             $d = $self->{type} ?
427             ($self->{type} eq 'rdft' ? $self->{coeff} :
428             die 'correl must involve a real function' ) :
429 1 0 33     6 $self->rdft && $self->{coeff};
    50          
430 1         1 $n2 = $n*$n;
431 1         7 _spctrm($n, $spctrm, $d, $self->{ip}, $self->{w}, $n2, 0);
432             }
433             }
434             else {
435 10         7 $d = [ @{$self->{data}}];
  10         637  
436 10         11 my ($data, @w);
437 10         9 my $k = $args{segments};
438 10         6 my $m = $args{number};
439 10 50 33     36 die 'Please specify a value for "number" in spctrm()'
440             if ($k and ! $m);
441 10 50       18 die "number ($m) must be an integer power of 2" unless _check_n($m);
442 10         13 my $m2 = $m+$m;
443 10         6 my $overlap = $args{overlap};
444 10 100       15 my $N = $overlap ? ($k+1)*$m : 2*$k*$m;
445 10 50       15 die "Need $N data points (data only has $n)" if $N > $n;
446 10 100       14 if ($win_fun) {
447 8 100       17 $win_fun = $win_sub->{$win_fun} if ref($win_fun) ne 'CODE';
448 8         16 for (my $j=0; $j<$m2; $j++) {
449 256         201 $w[$j] = $win_fun->($j, $m2);
450 256         420 $n2 += $w[$j]*$w[$j];
451             }
452             }
453             else {
454 2         2 $n2 = $m2;
455             }
456 10 100       13 if ($overlap) {
457 5         16 my @old = splice(@$d, 0, $m);
458 5         9 for (0..$k-1) {
459 80         46 push @{$data->[$_]}, @old;
  80         218  
460 80         154 my @new = splice(@$d, 0, $m);
461 80         69 push @{$data->[$_]}, @new;
  80         151  
462 80         178 @old = @new;
463 80 100       105 if ($win_fun) {
464 64         43 my $j=0;
465 64         37 $data->[$_] = [ map {$w[$j++]*$_} @{$data->[$_]}];
  2048         2028  
  64         47  
466             }
467             }
468             }
469             else {
470 5         7 for (0..$k-1) {
471 160         102 push @{$data->[$_]}, splice(@$d, 0, $m2);
  160         634  
472 160 100       279 if ($win_fun) {
473 128         73 my $j=0;
474 128         91 $data->[$_] = [ map {$w[$j++]*$_} @{$data->[$_]}];
  4096         3990  
  128         128  
475             }
476             }
477             }
478 10         9 my $tmp = [];
479 10         14 my $nip = int(3 + sqrt($m2));
480 10         13 my $nw = int(2 + 5*$m2/4);
481 10         27 my $ip = pack("i$nip", ());
482 10         15 my $w = pack("d$nw", ());
483 10         1121 _spctrm_bin($k, $m2, $spctrm, $data, \$ip, \$w, $n2, $tmp);
484             }
485 12         213 return $spctrm;
486             }
487             }
488              
489             sub mean {
490 1     1 1 6 my $self = shift;
491 1         1 my $sum = 0;
492 1         2 my ($n, $data);
493 1         1 my $flag = 0;
494 1 50       3 if ($data = shift) {
495 0 0       0 die 'Must call with an array reference'
496             unless ref($data) eq 'ARRAY';
497 0         0 $n = @$data;
498 0         0 $flag = 1;
499             }
500             else {
501 1         7 $data = $self->{data};
502 1         1 $n = $self->{n};
503             }
504 1         4 $sum += $_ for @$data;
505 1         2 my $mean = $sum / $n;
506 1 50       5 $self->{mean} = $mean unless $flag == 1;
507 1         2 return $mean;
508             }
509              
510             sub rms {
511 1     1 1 328 my $self = shift;
512 1         2 my $sum = 0;
513 1         1 my ($n, $data);
514 1 50       4 if ($data = shift) {
515 0 0       0 die 'Must call with an array reference'
516             unless ref($data) eq 'ARRAY';
517 0         0 $n = @$data;
518             }
519             else {
520 1         2 $data = $self->{data};
521 1         1 $n = $self->{n};
522             }
523 1         5 $sum += $_*$_ for @$data;
524 1         2 return sqrt($sum / $n);
525             }
526              
527             sub stdev {
528 1     1 1 722 my $self = shift;
529 1         2 my ($n, $data, $mean);
530 1 50       4 if ($data = shift) {
531 0 0       0 die 'Must call with an array reference'
532             unless ref($data) eq 'ARRAY';
533 0         0 $n = @$data;
534 0         0 $mean = $self->mean($data);
535             }
536             else {
537 1         1 $data = $self->{data};
538 1         2 $n = $self->{n};
539 1   33     3 $mean = $self->{mean} || $self->mean;
540             }
541 1 50       3 die 'Cannot find the standard deviation with n = 1'
542             if $n == 1;
543 1         1 my $sum = 0;
544 1         5 $sum += ($_ - $mean)*($_ - $mean) for @$data;
545 1         3 return sqrt($sum / ($n-1));
546             }
547              
548             sub range {
549 1     1 1 333 my $self = shift;
550 1         18 my ($n, $data);
551 1 50       4 if ($data = shift) {
552 0 0       0 die 'Must call with an array reference'
553             unless ref($data) eq 'ARRAY';
554 0         0 $n = @$data;
555             }
556             else {
557 1         1 $data = $self->{data};
558 1         3 $n = $self->{n};
559             }
560 1         1 my $min = $data->[0];
561 1         1 my $max = $data->[0];
562 1         3 for (@$data) {
563 5 50       10 $min = $_ if $_ < $min;
564 5 100       6 $max = $_ if $_ > $max;
565             }
566 1         4 return ($min, $max);
567             }
568              
569             sub median {
570 2     2 1 610 my $self = shift;
571 2         2 my ($n, $data);
572 2 50       5 if ($data = shift) {
573 0 0       0 die 'Must call with an array reference'
574             unless ref($data) eq 'ARRAY';
575 0         0 $n = @$data;
576             }
577             else {
578 2         3 $data = $self->{data};
579 2         3 $n = $self->{n};
580             }
581 2         9 my @sorted = sort {$a <=> $b} @$data;
  12         13  
582             return
583             (
584 2 100       13 ($n & 0x1)
585             ? $sorted[($n-1)/2]
586             : ($sorted[$n/2] + $sorted[$n/2-1])/2
587             );
588             }
589              
590             # Autoload methods go after =cut, and are processed by the autosplit program.
591              
592              
593             1;
594              
595             __END__