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   39175 use strict;
  3         3  
  3         66  
4 3     3   9 use warnings;
  3         3  
  3         52  
5              
6 3     3   38 use 5.008;
  3         6  
7              
8 3     3   8 use vars qw(@ISA);
  3         5  
  3         8440  
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.34';
18              
19             bootstrap Math::FFT $VERSION;
20              
21             # Preloaded methods go here.
22              
23             sub new {
24 21     21 1 280680 my ($class, $data) = @_;
25 21 50       73 die 'Must call constructor with an array reference for the data'
26             unless ref($data) eq 'ARRAY';
27 21   100     71 $data->[0] ||= 0; # keep warnings happy
28 21         27 my $n = @$data;
29 21         42 my $nip = int(3 + sqrt($n));
30 21         38 my $nw = int(2 + 5*$n/4);
31 21         115 my $ip = pack("i$nip", ());
32 21         1513 my $w = pack("d$nw", ());
33 21         150 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 20032 my ($self, $data) = @_;
47 1 50       5 die 'Must call clone with an array reference for the data'
48             unless ref($data) eq 'ARRAY';
49 1   50     3 $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         3 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         10 }, $class;
62             }
63              
64             # Complex Discrete Fourier Transform
65             sub cdft {
66 4     4 1 22 my $self = shift;
67 4         15 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         7 my $data = [ @{$self->{data}} ];
  4         3582  
70 4         11251 _cdft($n, 1, $data, $self->{ip}, $self->{w});
71 4         35 $self->{type} = 'cdft';
72 4         7 $self->{coeff} = $data;
73 4         12 return $data;
74             }
75              
76             # Inverse Complex Discrete Fourier Transform
77             sub invcdft {
78 4     4 1 297 my $self = shift;
79 4         5 my $data;
80 4         7 my $n = $self->{n};
81 4 50       12 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       13 unless $self->{type} eq 'cdft';
95 4         6 $data = [ @{$self->{coeff}} ];
  4         3250  
96             }
97 4         10964 _cdft($n, -1, $data, $self->{ip}, $self->{w});
98 4         11455 $_ *= 2.0/$n for (@$data);
99 4         15 return $data;
100             }
101              
102             # Real Discrete Fourier Transform
103             sub rdft {
104 7     7 1 21 my $self = shift;
105 7         10 my $n = $self->{n};
106 7 50       16 if (!_check_n($n))
107             {
108 0         0 die "data size ($n) must be an integer power of 2";
109             }
110 7         11 my $data = [ @{$self->{data}} ];
  7         947  
111 7         4226 _rdft($n, 1, $data, $self->{ip}, $self->{w});
112 7         20 $self->{type} = 'rdft';
113 7         8 $self->{coeff} = $data;
114 7         20 return $data;
115             }
116              
117             # Inverse Real Discrete Fourier Transform
118             sub invrdft {
119 3     3 1 610 my $self = shift;
120 3         3 my $data;
121 3         5 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         4 $data = [ @{$self->{coeff}} ];
  3         792  
133             }
134 3         3862 _rdft($n, -1, $data, $self->{ip}, $self->{w});
135 3         3796 $_ *= 2.0/$n for (@$data);
136 3         10 return $data;
137             }
138              
139             # Discrete Cosine Transform
140             sub ddct {
141 2     2 1 14 my $self = shift;
142 2         5 my $n = $self->{n};
143 2 50       6 die "data size ($n) must be an integer power of 2" unless _check_n($n);
144 2         4 my $data = [ @{$self->{data}} ];
  2         918  
145 2         5018 _ddct($n, -1, $data, $self->{ip}, $self->{w});
146 2         5 $self->{type} = 'ddct';
147 2         5 $self->{coeff} = $data;
148 2         5 return $data;
149             }
150              
151             # Inverse Discrete Cosine Transform
152             sub invddct {
153 2     2 1 306 my $self = shift;
154 2         4 my $data;
155 2         4 my $n = $self->{n};
156 2 50       6 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         803  
167             }
168 2         5 $data->[0] *= 0.5;
169 2         4197 _ddct($n, 1, $data, $self->{ip}, $self->{w});
170 2         3784 $_ *= 2.0/$n for (@$data);
171 2         7 return $data;
172             }
173              
174             # Discrete Sine Transform
175             sub ddst {
176 2     2 1 14 my $self = shift;
177 2         3 my $n = $self->{n};
178 2 50       7 die "data size ($n) must be an integer power of 2" unless _check_n($n);
179 2         5 my $data = [ @{$self->{data}} ];
  2         1006  
180 2         5061 _ddst($n, -1, $data, $self->{ip}, $self->{w});
181 2         6 $self->{type} = 'ddst';
182 2         4 $self->{coeff} = $data;
183 2         7 return $data;
184             }
185              
186             # Inverse Discrete Sine Transform
187             sub invddst {
188 2     2 1 288 my $self = shift;
189 2         3 my $data;
190 2         7 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       9 unless $self->{type} eq 'ddst';
201 2         3 $data = [ @{$self->{coeff}} ];
  2         790  
202             }
203 2         6 $data->[0] *= 0.5;
204 2         4240 _ddst($n, 1, $data, $self->{ip}, $self->{w});
205 2         3757 $_ *= 2.0/$n for (@$data);
206 2         10 return $data;
207             }
208              
209             # Cosine Transform of RDFT (Real Symmetric DFT)
210             sub dfct {
211 2     2 1 16 my $self = shift;
212 2         5 my $np1 = $self->{n};
213 2         3 my $n = $np1 - 1;
214 2 50       8 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         3 my $t = [];
217 2         5 my $data = [ @{$self->{data}} ];
  2         931  
218 2         6670 pdfct($nt, $n, $data, $t, $self->{ip}, $self->{w});
219 2         5 $self->{type} = 'dfct';
220 2         6 $self->{coeff} = $data;
221 2         205 return $data;
222             }
223              
224             # Inverse Cosine Transform of RDFT (Real Symmetric DFT)
225             sub invdfct {
226 2     2 1 337 my $self = shift;
227 2         2 my $data;
228 2         3 my $np1 = $self->{n};
229 2         5 my $n = $np1 - 1;
230 2 50       7 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       7 unless $self->{type} eq 'dfct';
240 2         3 $data = [ @{$self->{coeff}} ];
  2         783  
241             }
242 2         8 my $nt = int(2 + $n/2);
243 2         3 my $t = [];
244 2         3 $data->[0] *= 0.5;
245 2         2 $data->[$n] *= 0.5;
246 2         6162 pdfct($nt, $n, $data, $t, $self->{ip}, $self->{w});
247 2         5 $data->[0] *= 0.5;
248 2         3 $data->[$n] *= 0.5;
249 2         3768 $_ *= 2.0/$n for (@$data);
250 2         223 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         5 my $n = $self->{n};
257 2 50       6 die "data size ($n) must be an integer power of 2" unless _check_n($n);
258 2         5 my $data = [ @{$self->{data}} ];
  2         792  
259 2         7 my $nt = int(2 + $n/2);
260 2         2 my $t = [];
261 2         6345 pdfst($nt, $n, $data, $t, $self->{ip}, $self->{w});
262 2         6 $self->{type} = 'dfst';
263 2         3 $self->{coeff} = $data;
264 2         200 return $data;
265             }
266              
267             # Inverse Sine Transform of RDFT (Real Anti-symmetric DFT)
268             sub invdfst {
269 2     2 1 290 my $self = shift;
270 2         5 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       8 unless $self->{type} eq 'dfst';
282 2         2 $data = [ @{$self->{coeff}} ];
  2         766  
283             }
284 2         5 my $nt = int(2 + $n/2);
285 2         3 my $t = [];
286 2         5990 pdfst($nt, $n, $data, $t, $self->{ip}, $self->{w});
287 2         3769 $_ *= 2.0/$n for (@$data);
288 2         213 return $data;
289             }
290              
291             # check if $n is a power of 2
292             sub _check_n {
293 31     31   43 my ($n) = @_;
294              
295 31   33     216 return scalar($n == int($n) and $n > 0 and (! ($n & ($n-1))));
296             }
297              
298             sub correl {
299 4     4 1 12 my ($self, $other) = @_;
300 4         8 my $n = $self->{n};
301             my $d1 = $self->{type} ?
302 3         5 ($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       12 if (ref($other) eq 'Math::FFT') {
    50          
307             $d2 = $other->{type} ?
308 1         9 ($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         3 $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         4 my $corr = [];
320 4         40 _correl($n, $corr, $d1, $d2, $self->{ip}, $self->{w});
321 4         10 return $corr;
322             }
323              
324             sub convlv {
325 2     2 1 618 my ($self, $r) = @_;
326 2 50       6 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       3 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     7 $self->rdft && [ @{$self->{coeff}} ];
    50          
336 2         7 for (my $i=1; $i<=($m-1)/2; $i++) {
337 8         14 $respn->[$n-$i] = $respn->[$m-$i];
338             }
339 2         7 for (my $i=($m+3)/2; $i<=$n-($m-1)/2; $i++) {
340 14         22 $respn->[$i-1] = 0.0;
341             }
342 2         3 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 4 my ($self, $r) = @_;
349 1 50       4 die 'Must call deconvlv with an array reference for the response data'
350             unless ref($r) eq 'ARRAY';
351 1         1 my $respn = [ @$r ];
352 1         2 my $m = @$respn;
353 1 50       2 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         5 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       11 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 3040 my ($self, %args) = @_;
377 12         15 my %accept = map {$_ => 1} qw(window segments number overlap);
  48         67  
378 12         33 for (keys %args) {
379 39 50       57 die "`$_' is not a valid argument to spctrm" if not $accept{$_};
380             }
381 12         16 my $win_fun = $args{window};
382 12 100 100     43 if ($win_fun and ref($win_fun) ne 'CODE') {
383 7         8 my %accept = map {$_ => 1} qw(hamm hann welch bartlett);
  28         38  
384             die "`$win_fun' is not a known window function in spctrm"
385 7 50       17 if not $accept{$win_fun};
386             }
387             die 'Please specify a value for "segments" in spctrm()'
388 12 50 66     39 if ($args{number} and ! $args{segments});
389 12         12 my $n = $self->{n};
390 12         6 my $d;
391 12         11 my $n2 = 0;
392 12         13 my $spctrm = [];
393 12         11 my $win_sub = do {
394             my $h = sub {
395 64     64   36 my ($j, $n) = @_;
396 64         73 return (1 - cos($PI2*$j/$n))/2;
397 12         38 };
398              
399             +{
400             'hamm' => $h,
401             'hann' => $h,
402             'welch' => sub {
403 64     64   42 my ($j, $n) = @_;
404 64         76 return 1 - 4*($j-$n/2)*($j-$n/2)/$n/$n;
405             },
406             'bartlett' => sub {
407 80     80   52 my ($j, $n) = @_;
408 80         89 return 1 - abs(2*($j-$n/2)/$n);
409             },
410             }
411 12         56 };
412 12 100 33     39 if (not $args{segments} or ($args{segments} == 1 and not $args{number})) {
      66        
413 2 50       3 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         3  
416 1 50       4 $win_fun = $win_sub->{$win_fun} if ref($win_fun) ne 'CODE';
417 1         4 for (my $j=0; $j<$n; $j++) {
418 16         9 my $w = $win_fun->($j, $n);
419 16         10 $d->[$j] *= $w;
420 16         24 $n2 += $w * $w;
421             }
422 1         2 $n2 *= $n;
423 1         9 _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         8 _spctrm($n, $spctrm, $d, $self->{ip}, $self->{w}, $n2, 0);
432             }
433             }
434             else {
435 10         8 $d = [ @{$self->{data}}];
  10         654  
436 10         9 my ($data, @w);
437 10         8 my $k = $args{segments};
438 10         8 my $m = $args{number};
439 10 50 33     31 die 'Please specify a value for "number" in spctrm()'
440             if ($k and ! $m);
441 10 50       17 die "number ($m) must be an integer power of 2" unless _check_n($m);
442 10         12 my $m2 = $m+$m;
443 10         8 my $overlap = $args{overlap};
444 10 100       17 my $N = $overlap ? ($k+1)*$m : 2*$k*$m;
445 10 50       13 die "Need $N data points (data only has $n)" if $N > $n;
446 10 100       13 if ($win_fun) {
447 8 100       17 $win_fun = $win_sub->{$win_fun} if ref($win_fun) ne 'CODE';
448 8         13 for (my $j=0; $j<$m2; $j++) {
449 256         220 $w[$j] = $win_fun->($j, $m2);
450 256         397 $n2 += $w[$j]*$w[$j];
451             }
452             }
453             else {
454 2         3 $n2 = $m2;
455             }
456 10 100       13 if ($overlap) {
457 5         14 my @old = splice(@$d, 0, $m);
458 5         11 for (0..$k-1) {
459 80         55 push @{$data->[$_]}, @old;
  80         214  
460 80         156 my @new = splice(@$d, 0, $m);
461 80         73 push @{$data->[$_]}, @new;
  80         156  
462 80         166 @old = @new;
463 80 100       101 if ($win_fun) {
464 64         38 my $j=0;
465 64         39 $data->[$_] = [ map {$w[$j++]*$_} @{$data->[$_]}];
  2048         2081  
  64         53  
466             }
467             }
468             }
469             else {
470 5         8 for (0..$k-1) {
471 160         93 push @{$data->[$_]}, splice(@$d, 0, $m2);
  160         634  
472 160 100       283 if ($win_fun) {
473 128         79 my $j=0;
474 128         70 $data->[$_] = [ map {$w[$j++]*$_} @{$data->[$_]}];
  4096         4090  
  128         102  
475             }
476             }
477             }
478 10         8 my $tmp = [];
479 10         16 my $nip = int(3 + sqrt($m2));
480 10         12 my $nw = int(2 + 5*$m2/4);
481 10         26 my $ip = pack("i$nip", ());
482 10         15 my $w = pack("d$nw", ());
483 10         1145 _spctrm_bin($k, $m2, $spctrm, $data, \$ip, \$w, $n2, $tmp);
484             }
485 12         200 return $spctrm;
486             }
487             }
488              
489             sub mean {
490 1     1 1 4 my $self = shift;
491 1         1 my $sum = 0;
492 1         2 my ($n, $data);
493 1         1 my $flag = 0;
494 1 50       2 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         6 $data = $self->{data};
502 1         1 $n = $self->{n};
503             }
504 1         3 $sum += $_ for @$data;
505 1         2 my $mean = $sum / $n;
506 1 50       4 $self->{mean} = $mean unless $flag == 1;
507 1         1 return $mean;
508             }
509              
510             sub rms {
511 1     1 1 308 my $self = shift;
512 1         1 my $sum = 0;
513 1         2 my ($n, $data);
514 1 50       2 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         2 $n = $self->{n};
522             }
523 1         4 $sum += $_*$_ for @$data;
524 1         3 return sqrt($sum / $n);
525             }
526              
527             sub stdev {
528 1     1 1 649 my $self = shift;
529 1         1 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         2 $data = $self->{data};
538 1         1 $n = $self->{n};
539 1   33     4 $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         2 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 302 my $self = shift;
550 1         12 my ($n, $data);
551 1 50       3 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         2 $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       28 $min = $_ if $_ < $min;
564 5 100       12 $max = $_ if $_ > $max;
565             }
566 1         4 return ($min, $max);
567             }
568              
569             sub median {
570 2     2 1 591 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         1 $data = $self->{data};
579 2         2 $n = $self->{n};
580             }
581 2         7 my @sorted = sort {$a <=> $b} @$data;
  12         10  
582             return
583             (
584 2 100       10 ($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__