File Coverage

blib/lib/Math/WalshTransform.pm
Criterion Covered Total %
statement 82 288 28.4
branch 15 66 22.7
condition n/a
subroutine 14 18 77.7
pod 12 16 75.0
total 123 388 31.7


line stmt bran cond sub pod time code
1             # Math::WalshTransform.pm
2             #########################################################################
3             # This Perl module is Copyright (c) 2002, Peter J Billam #
4             # c/o P J B Computing, www.pjb.com.au #
5             # #
6             # This module is free software; you can redistribute it and/or #
7             # modify it under the same terms as Perl itself. #
8             #########################################################################
9              
10             package Math::WalshTransform;
11 1     1   19244 no strict;
  1         3  
  1         193  
12             $VERSION = '1.17';
13             # gives a -w warning, but I'm afraid $VERSION .= ''; would confuse CPAN
14             require DynaLoader;
15             require Exporter;
16             @ISA = qw(Exporter DynaLoader);
17             @EXPORT = qw(fht fhtinv fwt fwtinv biggest logical_convolution
18             logical_autocorrelation power_spectrum walsh2hadamard hadamard2walsh);
19             @EXPORT_OK = qw(sublist distance size average normalise product arr2txt);
20             %EXPORT_TAGS = (ALL => [@EXPORT,@EXPORT_OK]);
21             bootstrap Math::WalshTransform $VERSION;
22              
23             $PP = 0;
24              
25             sub fht {
26 2 50   2 1 1262 if (! $PP) { return &xs_fht(@_); }
  2         319  
27 0         0 my @mr = @_;
28 0         0 my $k = 1;
29 0         0 my $n = scalar @mr;
30 0         0 my $l = $n;
31 0         0 my $i; my $nl; my $nk; my $j;
  0         0  
  0         0  
32 0         0 while () {
33 1     1   824 $i = $[-1; $l = $l/2;
  1         429  
  1         7148  
  0         0  
  0         0  
34 0         0 for ($nl=1; $nl<= $l; $nl++) {
35 0         0 for ($nk=1; $nk<= $k; $nk++) {
36 0         0 $i++; $j = $i+$k;
  0         0  
37 0         0 $mr[$i] = ($mr[$i] + $mr[$j])/2;
38 0         0 $mr[$j] = $mr[$i] - $mr[$j];
39             }
40 0         0 $i = $j;
41             }
42 0         0 $k = 2*$k;
43 0 0       0 last if $k >= $n;
44             }
45 0 0       0 if ($k == $n) {
46 0         0 return @mr;
47             } else {
48 0         0 warn "Math::WalshTransform::fht \$n = $n but must be power of 2\n";
49 0         0 return ();
50             }
51             }
52             sub fhtinv {
53 2 50   2 1 331 if (! $PP) { return &xs_fhtinv(@_); }
  2         287  
54 0         0 my @mr = @_;
55 0         0 my $k = 1;
56 0         0 my $n = scalar @mr;
57 0         0 my $l = $n;
58 0         0 my $i; my $nl; my $nk; my $j;
  0         0  
  0         0  
59 0         0 while () {
60 0         0 $i = $[-1; $l = $l/2;
  0         0  
61 0         0 for ($nl=1; $nl<= $l; $nl++) {
62 0         0 for ($nk=1; $nk<= $k; $nk++) {
63 0         0 $i++; $j = $i+$k;
  0         0  
64 0         0 $mr[$i] = $mr[$i] + $mr[$j];
65 0         0 $mr[$j] = $mr[$i] - 2*$mr[$j];
66             }
67 0         0 $i = $j;
68             }
69 0         0 $k = 2*$k;
70 0 0       0 last if $k >= $n;
71             }
72 0 0       0 if ($k == $n) {
73 0         0 return @mr;
74             } else {
75 0         0 warn "Math::WalshTransform::fhtinv \$n = $n but must be power of 2\n";
76 0         0 return ();
77             }
78             }
79             sub fwt { # might be easier to Hadamard transform and shuffle the results
80 7 50   7 1 2553 if (! $PP) { return &xs_fwt(@_); }
  7         492  
81 0         0 my @mr = @_;
82 0         0 my $n = scalar @mr; my @nr; $#nr=$#mr;
  0         0  
  0         0  
83 0         0 my $k; my $l; my $i; my $nl; my $nk; my $kp1;
  0         0  
  0         0  
  0         0  
  0         0  
84              
85 0         0 my $m = 0; # log2($n)
86 0 0       0 my $tmp = 1; while () { last if $tmp >= $n; $tmp<<=1; $m++; }
  0         0  
  0         0  
  0         0  
  0         0  
87 0         0 my $alternate = $m & 1;
88              
89 0 0       0 if ($alternate) {
90 0         0 for ($k=$[; $k<$n-$[; $k+=2) {
91 0         0 $kp1 = $k+1;
92 0         0 $mr[$k] = ($mr[$k] + $mr[$kp1])/2;
93 0         0 $mr[$kp1] = $mr[$k] - $mr[$kp1];
94             }
95             } else {
96 0         0 for ($k=$[; $k<$n-$[; $k+=2) {
97 0         0 $kp1 = $k+1;
98 0         0 $nr[$k] = ($mr[$k] + $mr[$kp1])/2;
99 0         0 $nr[$kp1] = $nr[$k] - $mr[$kp1];
100             }
101             }
102              
103 0         0 $k = 1; my $nh = $n/2;
  0         0  
104 0         0 while () {
105 0 0       0 my $kh = $k; $k = $k+$k; $kp1 = $k+1; last if $kp1>$n;
  0         0  
  0         0  
  0         0  
106 0         0 $nh = $nh/2; $l = $[; $i = $[; $alternate = !$alternate;
  0         0  
  0         0  
  0         0  
107 0         0 for ($nl=1; $nl<=$nh; $nl++) {
108 0         0 for ($nk=1; $nk<=$kh; $nk++) {
109 0 0       0 if ($alternate) {
110 0         0 $mr[$l] = ($nr[$i] + $nr[$i+$k])/2;
111 0         0 $mr[$l+1] = $mr[$l] - $nr[$i+$k];
112 0         0 $mr[$l+2] = ($nr[$i+1] - $nr[$i+$kp1])/2;
113 0         0 $mr[$l+3] = $mr[$l+2] + $nr[$i+$kp1];
114             } else {
115 0         0 $nr[$l] = ($mr[$i] + $mr[$i+$k])/2;
116 0         0 $nr[$l+1] = $nr[$l] - $mr[$i+$k];
117 0         0 $nr[$l+2] = ($mr[$i+1] - $mr[$i+$kp1])/2;
118 0         0 $nr[$l+3] = $nr[$l+2] + $mr[$i+$kp1];
119             }
120 0         0 $l = $l+4; $i = $i+2;
  0         0  
121             }
122 0         0 $i = $i+$k;
123             }
124             }
125 0         0 return @mr;
126             }
127             sub fwtinv {
128 4 50   4 1 350 if (! $PP) { return &xs_fwtinv(@_); }
  4         416  
129 0         0 my @mr = @_; my $n = scalar @mr; my @nr; $#nr=$#mr;
  0         0  
  0         0  
  0         0  
130 0         0 my $k; my $l; my $i; my $nl; my $nk; my $kp1;
  0         0  
  0         0  
  0         0  
  0         0  
131              
132 0         0 my $m = 0; # log2($n)
133 0 0       0 my $tmp = 1; while () { last if $tmp >= $n; $tmp<<=1; $m++; }
  0         0  
  0         0  
  0         0  
  0         0  
134 0         0 my $alternate = $m & 1;
135              
136 0 0       0 if ($alternate) {
137 0         0 for ($k=$[; $k<$n-$[; $k+=2) {
138 0         0 $kp1 = $k+1;
139 0         0 $mr[$k] = $mr[$k] + $mr[$kp1];
140 0         0 $mr[$kp1] = $mr[$k] - $mr[$kp1] - $mr[$kp1];
141             }
142             } else {
143 0         0 for ($k=$[; $k<$n-$[; $k+=2) {
144 0         0 $kp1 = $k+1;
145 0         0 $nr[$k] = $mr[$k] + $mr[$kp1];
146 0         0 $nr[$kp1] = $mr[$k] - $mr[$kp1];
147             }
148             }
149              
150 0         0 $k = 1; my $nh = $n/2;
  0         0  
151 0         0 while () {
152 0 0       0 my $kh = $k; $k = $k+$k; $kp1 = $k+1; last if $kp1>$n;
  0         0  
  0         0  
  0         0  
153 0         0 $nh = $nh/2; $l = $[; $i = $[; $alternate = !$alternate;
  0         0  
  0         0  
  0         0  
154 0         0 for ($nl=1; $nl<=$nh; $nl++) {
155 0         0 for ($nk=1; $nk<=$kh; $nk++) {
156 0 0       0 if ($alternate) {
157 0         0 $mr[$l] = $nr[$i] + $nr[$i+$k];
158 0         0 $mr[$l+1] = $nr[$i] - $nr[$i+$k];
159 0         0 $mr[$l+2] = $nr[$i+1] - $nr[$i+$kp1];
160 0         0 $mr[$l+3] = $nr[$i+1] + $nr[$i+$kp1];
161             } else {
162 0         0 $nr[$l] = $mr[$i] + $mr[$i+$k];
163 0         0 $nr[$l+1] = $mr[$i] - $mr[$i+$k];
164 0         0 $nr[$l+2] = $mr[$i+1] - $mr[$i+$kp1];
165 0         0 $nr[$l+3] = $mr[$i+1] + $mr[$i+$kp1];
166             }
167 0         0 $l = $l+4; $i = $i+2;
  0         0  
168             }
169 0         0 $i = $i+$k;
170             }
171             }
172 0         0 return @mr;
173             }
174              
175 2     2 1 30 sub logical_convolution { my ($xref, $yref) = @_;
176 2 50       19 if (ref $xref ne 'ARRAY') { warn
  0 50       0  
177             "Math::WalshTransform::logical_convolution 1st arg must be array ref\n";
178 0         0 return undef;
179 0         0 } elsif (ref $yref ne 'ARRAY') { warn
180             "Math::WalshTransform::logical_convolution 2nd arg must be array ref\n";
181 0         0 return undef;
182             }
183 2         22 my @Fx = &fwt(@$xref); my @Fy = &fwt(@$yref);
  2         33  
184             # my @Fz; foreach ($[ .. $#Fx) { $Fz[$_] = $Fx[$_] * $Fy[$_]; } return @Fz;
185 2         131 return &fwtinv(&product(\@Fx, \@Fy));
186             }
187              
188 1     1 0 2969 sub old_logical_convolution { my ($xref, $yref) = @_;
189 1 50       11 if (ref $xref ne 'ARRAY') { warn
  0 50       0  
190             "Math::WalshTransform::logical_convolution 1st arg must be array ref\n";
191 0         0 return undef;
192 0         0 } elsif (ref $yref ne 'ARRAY') { warn
193             "Math::WalshTransform::logical_convolution 2nd arg must be array ref\n";
194 0         0 return undef;
195             }
196 1         2 local $[ = 0;
197 1         40 my @x = @$xref; my @y = @$yref;
  1         36  
198 1         3 my $n = scalar @x;
199 1         2 my @z; $#z=$#x;
  1         11  
200 1         28 my $j; my $k; my $sum;
  0         0  
201 1         10 for ($k=$[; $k<=$#x; $k++) {
202 512         497 $sum = 0.0;
203 512         2478 for ($j=$[; $j<=$#x; $j++) { $sum += $x[$j^$k] * $y[$j]; }
  262144         529478  
204 512         1570 $z[$k] = $sum/$n;
205             }
206 1         189 return @z;
207             }
208             sub logical_autocorrelation {
209 0     0 1 0 &logical_convolution(\@_,\@_);
210             }
211             sub power_spectrum {
212 1     1 1 339 &fwt( &logical_convolution(\@_,\@_) );
213             }
214             sub walsh2hadamard {
215 1     1 1 2011 my @h; $#h = $#_;
  1         16  
216 1         6 my @jw2jh = &jw2jh(scalar @_);
217 1         26 my $i; for ($i=$[; $i<=$#_; $i++) { $h[$jw2jh[$i]] = $_[$i]; }
  1         14  
  1024         1983  
218 1         208 return @h;
219             }
220             sub hadamard2walsh {
221 1     1 1 1954 my @w; $#w = $#_;
  1         25  
222 1         6 my @jw2jh = &jw2jh(scalar @_);
223 1         27 my $i; for ($i=$[; $i<=$#_; $i++) { $w[$i] = $_[$jw2jh[$i]]; }
  1         12  
  1024         1965  
224 1         223 return @w;
225             }
226              
227             # ---------------------- EXPORT_OK stuff ---------------------------
228              
229 1     1 1 279 sub biggest { my $k = shift @_; my @weeded = @_;
  1         4  
230 1         619 my $smallest;
231 1 50       5 if ($k <= 0) {
232 0         0 my $tot = 0.0; foreach (@weeded) { $tot += abs $_; }
  0         0  
  0         0  
233 0         0 $smallest = $tot / scalar @weeded;
234             } else {
235 1         8 my @sorted = sort { abs $b <=> abs $a } @weeded;
  35         44  
236 1         7 $smallest = abs $sorted[$[-1+$k];
237             }
238 1 100       3 foreach (@weeded) { if (abs $_ < $smallest) { $_=0.0; } }
  13         24  
  10         12  
239 1         9 return @weeded;
240             }
241 0     0 1 0 sub sublist { my ($aref, $offset, $length) = @_;
242 0 0       0 if (ref $aref ne 'ARRAY') {
243 0         0 warn "Math::WalshTransform::sublist 1st arg must be array ref\n"; return ();
  0         0  
244             }
245 0         0 my $first;
246 0 0       0 if ($offset<0) { $first = $#{$aref}+$offset+1; } else { $first=$offset; }
  0         0  
  0         0  
  0         0  
247 0         0 my $last;
248 0 0       0 if (! defined $length) { $last = $#{$aref};
  0 0       0  
  0         0  
249 0         0 } elsif ($length < 0) { $last = $#{$aref} + $length;
  0         0  
250 0         0 } else { $last = $first + $length - 1;
251             }
252             # warn "offset=$offset length=$length first=$first last=$last\n";
253 0         0 my @sublist = (); my $i;
  0         0  
254 0         0 for ($i=$first; $i<=$last; $i++) { push @sublist, ${$aref}[$i]; }
  0         0  
  0         0  
255 0         0 return @sublist;
256             }
257             # sub distance { my ($xref, $yref) = @_; # Euclidian metric
258             # if (ref $xref ne 'ARRAY') {
259             # warn "Math::WalshTransform::distance 1st arg must be array ref\n";
260             # return undef;
261             # } elsif (ref $yref ne 'ARRAY') {
262             # warn "Math::WalshTransform::distance 2nd arg must be array ref\n";
263             # return undef;
264             # }
265             # my $distance = 0.0; my $i; my $diff;
266             # for ($i=$[; $i<= $#$xref; $i++) {
267             # $diff = ${$xref}[$i] - ${$yref}[$i];
268             # $distance += $diff * $diff;
269             # }
270             # return sqrt $distance;
271             # }
272             # sub size {my $sum=0.0; foreach (@_) {$sum+=$_*$_;} return sqrt $sum;}
273             # sub normalise {
274             # my $size = &size(@_);
275             # my @normalised = ();
276             # foreach (@_) { push @normalised, $_/$size; }
277             # return @normalised;
278             #}
279             sub average {
280 1     1 1 2558 my $i = $[; my $j; my @sum;
  1         6  
281 1         3 foreach (@_) {
282 3 50       11 if (ref $_ ne 'ARRAY') {
283 0         0 warn "Math::WalshTransform::average argument $i must be array ref\n";
284 0         0 return undef;
285             }
286 3         13 for ($j=$[; $j<=$#$_; $j++) { $sum[$j] += ${$_}[$j]; }
  12         13  
  12         34  
287 3         7 $i++;
288             }
289 1         4 foreach (@sum) { $_ /= ($i-$[); }
  4         13  
290 1         6 return @sum;
291             }
292             sub arr2txt { # neat printing of arrays for debug use
293 0     0 0 0 my @txt; foreach (@_) { push @txt, sprintf('%g',$_); }
  0         0  
  0         0  
294 0         0 return join (' ',@txt)."\n";
295             }
296              
297             # ---------------------- infrastructure ---------------------------
298              
299 16     16 0 15 sub jw2jh { my $n = shift;
300 16 100       33 if ($n < 16) {
301 2 50       7 if ($n == 8) { return (0,4,6,2,3,7,5,1); }
  2         10  
302 0 0       0 if ($n == 4) { return (0,2,3,1); }
  0         0  
303 0 0       0 if ($n == 2) { return (0,1); }
  0         0  
304 0 0       0 if ($n == 1) { return (0); }
  0         0  
305 0         0 warn "jw2jh: n=$n must be power of 2\n"; return ();
  0         0  
306             }
307 14         16 my @half; foreach (&jw2jh($n/2)) { push @half, $_<<1; }
  14         36  
  2032         2079  
308 14         237 my @whole = @half; foreach (reverse @half) { push @whole, $_|1; }
  14         25  
  2032         2049  
309 14         626 return @whole;
310             }
311              
312             my $flag = 0;
313 0     0 0   sub gaussn { my $standdev = $_[$[];
314             # returns normal distribution around 0.0 by the Box-Muller rules
315 0 0         if (! $flag) {
316 0           $a = sqrt(-2.0 * log(rand));
317 0           $b = 6.28318531 * rand;
318 0           $flag = 1;
319 0           return ($standdev * $a * sin($b));
320             } else {
321 0           $flag = 0;
322 0           return ($standdev * $a * cos($b));
323             }
324             }
325             1;
326              
327             __END__