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   37 use 5.006;
  2         7  
4 2     2   11 use strict;
  2         5  
  2         62  
5 2     2   11 use warnings;
  2         4  
  2         105  
6              
7             our $VERSION = '1.02'; ## VERSION
8              
9             our @ISA = qw(Math::Function::Interpolator);
10              
11 2     2   12 use Carp qw(confess);
  2         4  
  2         99  
12 2     2   14 use List::MoreUtils qw(pairwise indexes);
  2         3  
  2         25  
13 2     2   1474 use Number::Closest::XS qw(find_closest_numbers_around);
  2         4  
  2         93  
14 2     2   12 use Scalar::Util qw(looks_like_number);
  2         4  
  2         1738  
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   48 my ($self) = @_;
45 23 100       57 return $self->{'_sorted_Xs'} if $self->{'_sorted_Xs'};
46 5         10 $self->{'_sorted_Xs'} = [sort { $a <=> $b } keys %{$self->points}];
  32         68  
  5         12  
47 5         14 return $self->{'_sorted_Xs'};
48             }
49              
50             sub _spline_points {
51 18     18   33 my ($self) = @_;
52              
53 18 100       46 return $self->{'_spline_points'} if $self->{'_spline_points'};
54              
55 4         12 my $points_ref = $self->points;
56 4         9 my $Xs = $self->_sorted_Xs;
57 4         9 my @Ys = map { $points_ref->{$_} } @$Xs;
  20         37  
58              
59             # First element is 0
60             # Second derivative of the Ys
61 4         10 my @y_2derivative = (0);
62 4         8 my @u = (0);
63 4         8 my $counter = @$Xs - 2;
64              
65 4         14 for my $i (1 .. $counter) {
66 12         35 my $sig = ($Xs->[$i] - $Xs->[$i - 1]) / ($Xs->[$i + 1] - $Xs->[$i - 1]);
67 12         28 my $p = $sig * $y_2derivative[$i - 1] + 2;
68 12         23 $y_2derivative[$i] = ($sig - 1) / $p;
69 12         39 $u[$i] = ($Ys[$i + 1] - $Ys[$i]) / ($Xs->[$i + 1] - $Xs->[$i]) - ($Ys[$i] - $Ys[$i - 1]) / ($Xs->[$i] - $Xs->[$i - 1]);
70 12         35 $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         11 push @y_2derivative, 0;
75              
76 4         13 for (my $i = $counter; $i > 0; $i--) {
77 12         34 $y_2derivative[$i] = $y_2derivative[$i] * $y_2derivative[$i + 1] + $u[$i];
78             }
79              
80 4     20   53 my %y_2derivative_combined = pairwise { $a => $b } @$Xs, @y_2derivative;
  20         62  
81              
82 4         21 $self->{'_spline_points'} = \%y_2derivative_combined;
83              
84 4         14 return $self->{'_spline_points'};
85             }
86              
87             sub _extrapolate_spline {
88 8     8   17 my ($self, $args) = @_;
89 8         12 my $x = $args->{x};
90 8         15 my $first = $args->{first};
91 8         13 my $second = $args->{second};
92 8         13 my $derivative2 = $args->{derivative2};
93              
94 8         24 my $derivative1 = (($second->{y} - $first->{y}) / ($second->{x} - $first->{x})) - (($second->{x} - $first->{x}) * $derivative2) / 6;
95              
96 8         22 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 6404 my ($self, $x) = @_;
110              
111 20 100       84 confess "sought_point[$x] must be a numeric" if !looks_like_number($x);
112 19         54 my $ap = $self->points;
113 19 50       82 return $ap->{$x} if defined $ap->{$x}; # No interpolation needed.
114              
115 19         49 my $Xs = $self->_sorted_Xs;
116 19 100       57 confess "cannot interpolate with fewer than 5 data points"
117             if scalar @$Xs < 5;
118              
119 18         38 my $splines = $self->_spline_points;
120              
121 18         30 my $y;
122 18 100 100     76 if ($x < $Xs->[0] or $x > $Xs->[-1]) {
123 8 100       24 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         54 y => $ap->{$second},
137             },
138             },
139             );
140             } else {
141 10         16 my ($first, $second) = @{find_closest_numbers_around($x, $Xs, 2)};
  10         61  
142              
143 10         26 my $range = $second - $first;
144              
145 10         24 my $A = ($second - $x) / $range;
146 10         19 my $B = 1 - $A;
147 10         44 my $C = ($A**3 - $A) * ($range**2) / 6;
148 10         27 my $D = ($B**3 - $B) * ($range**2) / 6;
149              
150 10         33 $y = $A * $ap->{$first} + $B * $ap->{$second} + $C * $splines->{$first} + $D * $splines->{$second};
151             }
152              
153 18         91 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