File Coverage

blib/lib/Math/Function/Interpolator/Cubic.pm
Criterion Covered Total %
statement 72 72 100.0
branch 13 14 92.8
condition 3 3 100.0
subroutine 10 10 100.0
pod 1 1 100.0
total 99 100 99.0


line stmt bran cond sub pod time code
1             package Math::Function::Interpolator::Cubic;
2              
3 2     2   36 use 5.006;
  2         8  
4 2     2   10 use strict;
  2         4  
  2         41  
5 2     2   9 use warnings;
  2         5  
  2         115  
6              
7             our $VERSION = '1.03'; ## VERSION
8              
9             our @ISA = qw(Math::Function::Interpolator);
10              
11 2     2   11 use Carp qw(confess);
  2         3  
  2         83  
12 2     2   11 use Number::Closest::XS qw(find_closest_numbers_around);
  2         4  
  2         69  
13 2     2   10 use Scalar::Util qw(looks_like_number);
  2         4  
  2         1721  
14              
15             =head1 NAME
16              
17             Math::Function::Interpolator::Cubic
18              
19             =head1 SYNOPSIS
20              
21             use Math::Function::Interpolator::Cubic;
22              
23             my $interpolator = Math::Function::Interpolator::Cubic->new(
24             points => {1=>2,2=>3,3=>4,4=>5,5=>6,6=>7}
25             );
26              
27             $interpolator->cubic(2.5);
28              
29             =head1 DESCRIPTION
30              
31             Math::Function::Interpolator::Cubic helps you to do the interpolation calculation with cubic method.
32             It solves the interpolated_y given point_x and a minimum of 5 data points.
33              
34             =head1 FIELDS
35              
36             =head2 points (REQUIRED)
37              
38             HashRef of points for interpolations
39              
40             =cut
41              
42             sub _sorted_Xs {
43 23     23   53 my ($self) = @_;
44 23 100       58 return $self->{'_sorted_Xs'} if $self->{'_sorted_Xs'};
45 5         10 $self->{'_sorted_Xs'} = [sort { $a <=> $b } keys %{$self->points}];
  32         108  
  5         14  
46 5         33 return $self->{'_sorted_Xs'};
47             }
48              
49             sub _spline_points {
50 18     18   32 my ($self) = @_;
51              
52 18 100       56 return $self->{'_spline_points'} if $self->{'_spline_points'};
53              
54 4         15 my $points_ref = $self->points;
55 4         11 my $Xs = $self->_sorted_Xs;
56 4         12 my @Ys = map { $points_ref->{$_} } @$Xs;
  20         39  
57              
58             # First element is 0
59             # Second derivative of the Ys
60 4         10 my @y_2derivative = (0);
61 4         9 my @u = (0);
62 4         14 my $counter = @$Xs - 2;
63              
64 4         15 for my $i (1 .. $counter) {
65 12         48 my $sig = ($Xs->[$i] - $Xs->[$i - 1]) / ($Xs->[$i + 1] - $Xs->[$i - 1]);
66 12         27 my $p = $sig * $y_2derivative[$i - 1] + 2;
67 12         31 $y_2derivative[$i] = ($sig - 1) / $p;
68 12         41 $u[$i] = ($Ys[$i + 1] - $Ys[$i]) / ($Xs->[$i + 1] - $Xs->[$i]) - ($Ys[$i] - $Ys[$i - 1]) / ($Xs->[$i] - $Xs->[$i - 1]);
69 12         44 $u[$i] = (($u[$i] * 6) / ($Xs->[$i + 1] - $Xs->[$i - 1]) - $sig * $u[$i - 1]) / $p;
70             }
71              
72             # Last element is 0
73 4         17 push @y_2derivative, 0;
74              
75 4         16 for (my $i = $counter; $i > 0; $i--) {
76 12         41 $y_2derivative[$i] = $y_2derivative[$i] * $y_2derivative[$i + 1] + $u[$i];
77             }
78              
79 4         14 my %y_2derivative_combined = map { $Xs->[$_] => $y_2derivative[$_] } 0 .. $#y_2derivative;
  20         59  
80              
81 4         21 $self->{'_spline_points'} = \%y_2derivative_combined;
82              
83 4         13 return $self->{'_spline_points'};
84             }
85              
86             sub _extrapolate_spline {
87 8     8   18 my ($self, $args) = @_;
88 8         15 my $x = $args->{x};
89 8         11 my $first = $args->{first};
90 8         17 my $second = $args->{second};
91 8         9 my $derivative2 = $args->{derivative2};
92              
93 8         25 my $derivative1 = (($second->{y} - $first->{y}) / ($second->{x} - $first->{x})) - (($second->{x} - $first->{x}) * $derivative2) / 6;
94              
95 8         22 return $first->{y} - ($first->{x} - $x) * $derivative1;
96             }
97              
98             =head1 METHODS
99              
100             =head2 cubic
101              
102             cubic
103              
104             =cut
105              
106             # Returns the interpolated_y given point_x and a minimum of 5 data points
107             sub cubic {
108 20     20 1 7514 my ($self, $x) = @_;
109              
110 20 100       90 confess "sought_point[$x] must be a numeric" if !looks_like_number($x);
111 19         57 my $ap = $self->points;
112 19 50       65 return $ap->{$x} if defined $ap->{$x}; # No interpolation needed.
113              
114 19         46 my $Xs = $self->_sorted_Xs;
115 19 100       63 confess "cannot interpolate with fewer than 5 data points"
116             if scalar @$Xs < 5;
117              
118 18         38 my $splines = $self->_spline_points;
119              
120 18         28 my $y;
121 18 100 100     79 if ($x < $Xs->[0] or $x > $Xs->[-1]) {
122 8 100       26 my ($spline_key, $first, $second) =
123             $x < $Xs->[0]
124             ? ($Xs->[1], $Xs->[0], $Xs->[1])
125             : ($Xs->[-2], $Xs->[-2], $Xs->[-1]);
126             $y = $self->_extrapolate_spline({
127             x => $x,
128             derivative2 => $splines->{$spline_key},
129             first => {
130             x => $first,
131             y => $ap->{$first},
132             },
133             second => {
134             x => $second,
135 8         41 y => $ap->{$second},
136             },
137             },
138             );
139             } else {
140 10         28 my ($first, $second) = @{find_closest_numbers_around($x, $Xs, 2)};
  10         64  
141              
142 10         30 my $range = $second - $first;
143              
144 10         25 my $A = ($second - $x) / $range;
145 10         17 my $B = 1 - $A;
146 10         50 my $C = ($A**3 - $A) * ($range**2) / 6;
147 10         26 my $D = ($B**3 - $B) * ($range**2) / 6;
148              
149 10         34 $y = $A * $ap->{$first} + $B * $ap->{$second} + $C * $splines->{$first} + $D * $splines->{$second};
150             }
151              
152 18         66 return $y;
153             }
154              
155             =head1 AUTHOR
156              
157             Binary.com, C<< >>
158              
159             =head1 BUGS
160              
161             Please report any bugs or feature requests to C, or through
162             the web interface at L. I will be notified, and then you'll
163             automatically be notified of progress on your bug as I make changes.
164              
165              
166              
167              
168             =head1 SUPPORT
169              
170             You can find documentation for this module with the perldoc command.
171              
172             perldoc Math::Function::Interpolator
173              
174              
175             You can also look for information at:
176              
177             =over 4
178              
179             =item * RT: CPAN's request tracker (report bugs here)
180              
181             L
182              
183             =item * AnnoCPAN: Annotated CPAN documentation
184              
185             L
186              
187             =item * CPAN Ratings
188              
189             L
190              
191             =item * Search CPAN
192              
193             L
194              
195             =back
196              
197              
198             =head1 ACKNOWLEDGEMENTS
199              
200             =cut
201              
202             1; # End of Math::Function::Interpolator::Cubic