File Coverage

blib/lib/Math/Function/Interpolator/Cubic.pm
Criterion Covered Total %
statement 75 75 100.0
branch 13 14 92.8
condition 3 3 100.0
subroutine 12 12 100.0
pod 1 1 100.0
total 104 105 99.0


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