File Coverage

blib/lib/Math/Interpolate.pm
Criterion Covered Total %
statement 133 139 95.6
branch 50 80 62.5
condition 12 21 57.1
subroutine 8 9 88.8
pod 4 5 80.0
total 207 254 81.5


line stmt bran cond sub pod time code
1             package Math::Interpolate;
2              
3             require 5.004_01;
4              
5 1     1   1684 use strict;
  1         2  
  1         38  
6 1     1   5 use Exporter;
  1         2  
  1         51  
7 1     1   813 use Math::IntervalSearch qw(interval_search);
  1         12  
  1         78  
8 1     1   7 use vars qw(@EXPORT_OK @ISA $VERSION);
  1         2  
  1         2050  
9              
10             @EXPORT_OK = qw(derivatives constant_interpolate
11             linear_interpolate robust_interpolate);
12             @ISA = qw(Exporter);
13             $VERSION = '1.06';
14             $VERSION = eval $VERSION;
15              
16             sub derivatives {
17 1004     1004 1 1445 my $X = shift;
18 1004 50       1913 return unless defined($X);
19 1004 50       1920 return unless ref($X);
20              
21 1004         1220 my $Y = shift;
22 1004 50       1791 return unless defined($Y);
23 1004 50       1844 return unless ref($Y);
24              
25 1004         1217 my $num_x = @$X;
26 1004         1091 my $num_y = @$Y;
27              
28 1004 50       1765 return unless $num_x == $num_y;
29              
30 1004 50       1898 if ( $num_x < 2 ) {
31 0         0 return ();
32             }
33              
34             # Set up the derivative array.
35 1004         1129 my @deriv;
36            
37             # If there for two input points, use a straight line as the derivative.
38 1004 100       1833 if ( $num_x == 2 ) {
39 1         4 $deriv[0] = ($Y->[1] - $Y->[0]) / ($X->[1] - $X->[0]);
40 1         2 $deriv[1] = $deriv[0];
41 1         5 return @deriv;
42             }
43              
44             # Calculate the derivatives for the interior points. This loop uses
45             # a total of 6 points to calculate the derivative at any one
46             # point. And when the loop moves along in increasing array
47             # position, the same data point is used three times. So instead of
48             # reading the correct value from the array three times, just shift
49             # the values down by copying them from one variable to the next.
50 1003         1035 my $xi;
51 1003         1349 my $xj = $X->[0];
52 1003         1406 my $xk = $X->[1];
53 1003         1066 my $yi;
54 1003         1155 my $yj = $Y->[0];
55 1003         1309 my $yk = $Y->[1];
56              
57 1003         2393 for (my $i=1; $i<$num_x-1; ++$i) {
58 3009         3366 $xi = $xj;
59 3009         3136 $xj = $xk;
60 3009         4138 $xk = $X->[$i+1];
61 3009         2989 $yi = $yj;
62 3009         3137 $yj = $yk;
63 3009         3537 $yk = $Y->[$i+1];
64              
65 3009         5473 my $r1 = ($xk - $xj)*($xk - $xj) + ($yk - $yj)*($yk - $yj);
66 3009         4811 my $r2 = ($xj - $xi)*($xj - $xi) + ($yj - $yi)*($yj - $yi);
67              
68 3009         10777 $deriv[$i] =
69             ( ($yj - $yi)*$r1 + ($yk - $yj)*$r2 ) /
70             ( ($xj - $xi)*$r1 + ($xk - $xj)*$r2 );
71             }
72              
73             # Calculate the derivative at the first point, (x(0),y(0)).
74 1003         1149 my $i = 0;
75 1003         1086 my $j = 1;
76 1003         2002 my $slope = ($Y->[$j] - $Y->[$i])/($X->[$j] - $X->[$i]);
77 1003 100 66     6486 if ( (($slope >= 0) && ($slope >= $deriv[$j])) ||
      66        
      33        
78             (($slope <= 0) && ($slope <= $deriv[$j])) ) {
79 1         4 $deriv[0] = 2*$slope - $deriv[1];
80             }
81             else {
82 1002         2448 $deriv[0] = $slope + (abs($slope) * ($slope - $deriv[1])) /
83             (abs($slope) + abs($slope - $deriv[1]) );
84             }
85              
86             # Calculate the derivative at the last point.
87 1003         1196 $i = $num_x - 2;
88 1003         1209 $j = $num_x - 1;
89 1003         2097 $slope = ($Y->[$j] - $Y->[$i])/($X->[$j] - $X->[$i]);
90 1003 100 66     5067 if ( (($slope >= 0) && ($slope >= $deriv[$i])) ||
      33        
      66        
91             (($slope <= 0) && ($slope <= $deriv[$i])) ) {
92 1002         1704 $deriv[$j] = 2*$slope - $deriv[$i];
93             }
94             else {
95 1         19 $deriv[$j] = $slope + (abs($slope) * ($slope - $deriv[$i])) /
96             (abs($slope) + abs($slope - $deriv[$i]) );
97             }
98              
99 1003         4272 @deriv;
100             }
101              
102             sub constant_interpolate {
103 8     8 1 13 my $x = shift;
104 8 50       15 return unless defined($x);
105              
106 8         11 my $X = shift;
107 8 50       17 return unless defined($X);
108 8 50       15 return unless ref($X);
109              
110 8         10 my $Y = shift;
111 8 50       29 return unless defined($Y);
112 8 50       15 return unless ref($Y);
113              
114 8         10 my $num_x = @$X;
115 8         10 my $num_y = @$Y;
116 8 50       16 return unless $num_x == $num_y;
117              
118             # Find where the point to be interpolated lies in the input sequence.
119             # If the x value lies outside of the X sequence, use the value at either
120             # the beginning or the end of the sequence.
121 8         18 my $j = interval_search($x, $X);
122 8 100       26 if ( $j < 0 ) {
    50          
123 1         1 $j = 0;
124             }
125             elsif ( $j > $num_x - 1 ) {
126 0         0 $j = $num_x - 1;
127             }
128              
129             # Return the Y value at the point.
130 8         38 $Y->[$j];
131             }
132              
133             sub linear_interpolate {
134 2003     2003 1 147157 my $x = shift;
135 2003 50       4237 return unless defined($x);
136              
137 2003         2431 my $X = shift;
138 2003 50       3514 return unless defined($X);
139 2003 50       3874 return unless ref($X);
140              
141 2003         2322 my $Y = shift;
142 2003 50       3427 return unless defined($Y);
143 2003 50       3565 return unless ref($Y);
144              
145 2003         2589 my $num_x = @$X;
146 2003         2118 my $num_y = @$Y;
147 2003 50       3582 return unless $num_x == $num_y;
148              
149             # Find where the point to be interpolated lies in the input sequence.
150             # If the point lies outside, then coerce the index value to be legal for
151             # the routine to work. Remember, this is only an interpreter, not an
152             # extrapolator.
153 2003         4873 my $j = interval_search($x, $X);
154 2003 100       5568 if ( $j < 0 ) {
    100          
155 1         1 $j = 0;
156             }
157             elsif ( $j >= $num_x - 1 ) {
158 2         6 $j = $num_x - 2;
159             }
160 2003         2496 my $k = $j + 1;
161              
162             # Calculate the linear slope between the two points.
163 2003         4024 my $dy = ($Y->[$k] - $Y->[$j]) / ($X->[$k] - $X->[$j]);
164              
165             # Use the straight line between the two points to interpolate.
166 2003         3594 my $y = $dy*($x - $X->[$j]) + $Y->[$j];
167              
168 2003 100       6528 return wantarray ? ($y, $dy) : $y;
169             }
170              
171             sub robust_interpolate {
172 2002     2002 1 6901 my $x = shift;
173 2002 50       3651 return unless defined($x);
174              
175 2002         2458 my $X = shift;
176 2002 50       3457 return unless defined($X);
177 2002 50       3851 return unless ref($X);
178              
179 2002         2337 my $Y = shift;
180 2002 50       3455 return unless defined($Y);
181 2002 50       3592 return unless ref($Y);
182              
183 2002         2482 my $num_x = @$X;
184 2002         2118 my $num_y = @$Y;
185 2002 50       3437 return unless $num_x == $num_y;
186              
187             # Calculate the derivative if it wasn't passed in.
188 2002         2190 my $dY = shift;
189 2002 100 66     9315 unless (defined($dY) and ref($dY)) {
190 1001         1787 $dY = [ derivatives($X, $Y) ];
191             }
192              
193             # Find where the point to be interpolated lies in the input
194             # sequence. If the point lies outside, then coerce the index value
195             # to be legal for the routine to work. Remember, this is only an
196             # interpreter, not an extrapolator.
197 2002         5344 my $j = interval_search($x, $X);
198 2002 50       5283 if ( $j < 0 ) {
    100          
199 0         0 $j = 0;
200             }
201             elsif ( $j >= $num_x - 1 ) {
202 2         6 $j = $num_x - 2;
203             }
204 2002         2569 my $k = $j + 1;
205              
206             # Calculate a few variables that will be used frequently.
207 2002         2745 my $xj = $X->[$j];
208 2002         2357 my $xk = $X->[$k];
209 2002         2247 my $yj = $Y->[$j];
210 2002         2516 my $yk = $Y->[$k];
211 2002         2982 my $slope = ($yk - $yj) / ($xk - $xj);
212 2002         3442 my $y0 = $yj + $slope * ($x - $xj);
213 2002         3348 my $dely0 = $yj + $dY->[$j] * ($x - $xj) - $y0;
214 2002         3048 my $dely1 = $yk + $dY->[$k] * ($x - $xk) - $y0;
215              
216             # Calculate the derivatives of the three variables above with respest
217             # to x.
218 2002         2276 my $d_y0 = $slope;
219 2002         2988 my $d_dely0 = $dY->[$j] - $d_y0;
220 2002         2627 my $d_dely1 = $dY->[$k] - $d_y0;
221              
222             # Calculate the interpolated y and dy values.
223 2002         2329 my $dely_sign = $dely0*$dely1;
224 2002         2132 my $y;
225             my $dy;
226 2002 100       4317 if ($dely_sign == 0) {
227 10         570 $y = $y0;
228 10         12 $dy = $d_y0;
229 10 50       51 return wantarray ? ($y0, $d_y0) : $y0;
230             }
231              
232 1992         2381 my $dely_sum = $dely0 + $dely1;
233 1992 100       8121 if ($dely_sign > 0) {
234 1395         1852 $y = $y0 + $dely_sign/$dely_sum;
235 1395         3203 $dy = $d_y0 + ($dely_sum*($dely0*$d_dely1 + $d_dely0*$dely1) -
236             $dely_sign*($d_dely0 + $d_dely1)) / ($dely_sum*$dely_sum);
237             }
238             else {
239 597         835 my $x_tmp = 2*$x - $xj - $xk;
240 597         1024 $y = $y0 + $dely_sign*$x_tmp/(($dely0 - $dely1)*($xk - $xj));
241 597         2111 $dy = $d_y0 + (($dely0 - $dely1)*($xk - $xj)*
242             ($d_dely0*$dely1*$x_tmp +
243             $dely0*$d_dely1*$x_tmp +
244             $dely_sign*2) -
245             $dely_sign*$x_tmp*
246             (($xk - $xj)*($d_dely0 - $d_dely1))) /
247             (($dely0 - $dely1)*($dely0 - $dely1)*($xk - $xj)*($xk - $xj));
248             }
249              
250 1992 50       7658 return wantarray ? ($y, $dy) : $y;
251             }
252              
253             sub degenerate {
254 0     0 0   my ($x1, $y1, $dy1, $x2, $y2, $dy2) = @_;
255 0           my $slope = ($y2 - $y1)/($x2 - $x1);
256 0 0         (sleep == $dy1) && ($dy1 != $dy2);
257             }
258              
259             1;
260              
261             __END__