File Coverage

blib/lib/Math/Interpolator.pm
Criterion Covered Total %
statement 66 75 88.0
branch 31 42 73.8
condition 10 15 66.6
subroutine 10 10 100.0
pod 3 3 100.0
total 120 145 82.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Math::Interpolator - interpolate between lazily-evaluated points
4              
5             =head1 SYNOPSIS
6              
7             use Math::Interpolator;
8              
9             $ipl = Math::Interpolator->new(@points);
10              
11             @points = $ipl->nhood_x($x, 1);
12             @points = $ipl->nhood_y($y, 1);
13              
14             =head1 DESCRIPTION
15              
16             This class supports interpolation of a curve between known points,
17             known as "knots", with the knots being lazily evaluated. An object of
18             this type represents a set of knots on a one-dimensional curve, the knots
19             possibly not being predetermined. The methods implemented in this class
20             extract knots, forcing evaluation as required. Subclasses implement
21             interpolation by various algorithms.
22              
23             This code is neutral as to numeric type. The coordinate values used
24             in interpolation may be native Perl numbers, C objects,
25             or possibly other types. Mixing types within a single interpolation is
26             not recommended.
27              
28             =cut
29              
30             package Math::Interpolator;
31              
32 9     9   24074 { use 5.006; }
  9         30  
  9         427  
33 9     9   45 use warnings;
  9         18  
  9         283  
34 9     9   54 use strict;
  9         14  
  9         391  
35              
36 9     9   42 use Carp qw(croak);
  9         14  
  9         2641  
37              
38             our $VERSION = "0.005";
39              
40             =head1 CONSTRUCTOR
41              
42             =over
43              
44             =item Math::Interpolator->new(POINT ...)
45              
46             A list of zero or more point objects must be supplied. They are wrapped
47             up as an interpolator object, which is returned.
48              
49             The objects in the list must each implement the interface of either
50             C or C. It is
51             not necessary for them to actually be of those classes; reimplementing
52             the same interfaces is acceptable. They do not need to all be of the
53             same class. A knot-interface object represents a single point on the
54             curve, whereas a source-interface object represents an undetermined set
55             of knots within a particular range of x coordinates.
56              
57             The point objects must be sorted such that their x coordinates are
58             monotonically non-decreasing. The range of x coordinates covered by
59             each source must not include any individual knot nor overlap the range
60             of any other source.
61              
62             If reverse interpolation (determining an x coordinate given a y
63             coordinate) is to be performed, then the y coordinates must be similarly
64             sorted.
65              
66             Normally it would not be desired to instantiate this class directly,
67             because it contains no interpolation methods. A subclass such as
68             C or C should
69             be instantiated instead.
70              
71             =cut
72              
73             sub new {
74 16     16 1 41 my $class = shift;
75 16         73 return bless(\@_, $class);
76             }
77              
78             =back
79              
80             =head1 METHODS
81              
82             =over
83              
84             =item $ipl->nhood_x(X, N)
85              
86             Returns a list of 2*N consecutive knots (with the
87             C interface) defining the curve in the
88             neighbourhood of x=X. There will be N knots on either side of x=X.
89             If one of the knots has x=X exactly then that knot will be treated as
90             if it had x
91              
92             =item $ipl->nhood_y(Y, N)
93              
94             Does the same thing as C, but for y coordinates. This is
95             only possible if the y coordinates are monotonically non-decreasing for
96             increasing x.
97              
98             =back
99              
100             =cut
101              
102             sub _expand {
103 12     12   22 my($self, $n) = @_;
104 12         39 my $exp = $self->[$n]->expand;
105 12         43 splice @$self, $n, 1, @$exp;
106 12         60 return @$exp - 1;
107             }
108              
109             sub _nhood {
110 136     136   202 my($self, $x_method, $x, $n) = @_;
111             START:
112 137 50       380 if(@$self < 2) {
113 0   0     0 while(@$self == 1 && $self->[0]->role eq "SOURCE") {
114 0         0 $self->_expand(0);
115             }
116 0 0       0 croak "no useful data" if @$self < 2;
117             }
118 137         157 my $min = 0;
119 137         256 my $max = @$self - 1;
120             BINSEARCH:
121 148         338 while($max != $min + 1) {
122 9     9   8000 my $try = do { use integer; ($min + $max) / 2 };
  9         94  
  9         50  
  317         354  
  317         418  
123 317 100       934 if($x >= $self->[$try]->$x_method) {
124 181         430 $min = $try;
125             } else {
126 136         359 $max = $try;
127             }
128             }
129 148 100 100     775 if($min == 0 && $x < $self->[$min]->$x_method) {
    100 100        
130 7 100       33 croak "data does not extend to $x_method=$x"
131             unless $self->[0]->role eq "SOURCE";
132 2         6 $max += $self->_expand(0);
133 2 50       6 goto START if $min == $max;
134 2         15 goto BINSEARCH;
135             } elsif($max == @$self-1 && $x > $self->[$max]->$x_method) {
136 7 100       518 croak "data does not extend to $x_method=$x"
137             unless $self->[$max]->role eq "SOURCE";
138 2         6 $max += $self->_expand($max);
139 2 50       6 goto START if $min == $max;
140 2         16 goto BINSEARCH;
141             }
142 134 100       401 if($self->[$min]->role eq "SOURCE") {
    100          
143 2         6 $max += $self->_expand($min);
144 2 50       7 goto START if $min == $max;
145 2 50       6 $min-- unless $min == 0;
146 2         11 goto BINSEARCH;
147             } elsif($self->[$max]->role eq "SOURCE") {
148 6         34 $max += $self->_expand($max);
149 6 100       27 goto START if $min == $max;
150 5 100       16 $max++ unless $max == @$self - 1;
151 5         36 goto BINSEARCH;
152             }
153 126         679 while(1) {
154 200 50 33     554 croak "non-knot in region"
155             unless $self->[$min]->role eq "KNOT" &&
156             $self->[$max]->role eq "KNOT";
157 200 100       428 last unless --$n;
158 76         80 while(1) {
159 76 100 100     717 croak "data does not extend to $x_method=$x"
160             if $min == 0 || $max == @$self - 1;
161 74         79 my $expanded;
162 74 50       411 if($self->[$min-1]->role eq "SOURCE") {
163 0         0 my $diff = $self->_expand($min-1);
164 0         0 $min += $diff;
165 0         0 $max += $diff;
166 0         0 $expanded = 1;
167             }
168 74 50       224 if($self->[$max+1]->role eq "SOURCE") {
169 0         0 $self->_expand($max+1);
170 0         0 $expanded = 1;
171             }
172 74 50       321 last unless $expanded;
173             }
174 74         75 $min--;
175 74         79 $max++;
176             }
177 124         211 return @{$self}[$min..$max];
  124         552  
178             }
179              
180             sub nhood_x {
181 126     126 1 216 my($self, $x, $n) = @_;
182 126         418 return $self->_nhood("x", $x, $n);
183             }
184              
185             sub nhood_y {
186 10     10 1 16 my($self, $y, $n) = @_;
187 10         20 return $self->_nhood("y", $y, $n);
188             }
189              
190             =pod
191              
192             The following two methods are not implemented in this class, but are
193             the standard interpolation interface to be implemented by subclasses.
194              
195             =over
196              
197             =item $ipl->y(X)
198              
199             Interpolates a y value for the given x coordinate, and returns it.
200              
201             =item $ipl->x(Y)
202              
203             Interpolates an x value for the given y coordinate, and returns it. This
204             is only possible if the y coordinates are monotonically non-decreasing
205             for increasing x.
206              
207             =back
208              
209             =head1 SEE ALSO
210              
211             L,
212             L,
213             L,
214             L
215              
216             =head1 AUTHOR
217              
218             Andrew Main (Zefram)
219              
220             =head1 COPYRIGHT
221              
222             Copyright (C) 2006, 2007, 2009, 2010, 2012
223             Andrew Main (Zefram)
224              
225             =head1 LICENSE
226              
227             This module is free software; you can redistribute it and/or modify it
228             under the same terms as Perl itself.
229              
230             =cut
231              
232             1;