line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Math::CPWLF; |
2
|
|
|
|
|
|
|
|
3
|
5
|
|
|
5
|
|
142024
|
use warnings; |
|
5
|
|
|
|
|
14
|
|
|
5
|
|
|
|
|
181
|
|
4
|
5
|
|
|
5
|
|
111
|
use strict; |
|
5
|
|
|
|
|
16
|
|
|
5
|
|
|
|
|
180
|
|
5
|
|
|
|
|
|
|
|
6
|
5
|
|
|
5
|
|
29
|
use Carp; |
|
5
|
|
|
|
|
14
|
|
|
5
|
|
|
|
|
376
|
|
7
|
5
|
|
|
5
|
|
5072
|
use Want; |
|
5
|
|
|
|
|
37862
|
|
|
5
|
|
|
|
|
380
|
|
8
|
5
|
|
|
5
|
|
372
|
use List::Util; |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
877
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
use overload |
11
|
|
|
|
|
|
|
fallback => 1, |
12
|
|
|
|
|
|
|
'&{}' => sub |
13
|
|
|
|
|
|
|
{ |
14
|
473
|
|
|
473
|
|
352981
|
my $self = $_[0]; |
15
|
473
|
|
|
|
|
1319
|
return _top_interp_closure( $self, $self->{_opts} ) |
16
|
5
|
|
|
5
|
|
10854
|
}; |
|
5
|
|
|
|
|
6654
|
|
|
5
|
|
|
|
|
54
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 NAME |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Math::CPWLF - interpolation using nested continuous piece-wise linear functions |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 VERSION |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
Version 0.15 |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=cut |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
our $VERSION = '0.15'; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=head1 SYNOPSIS |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
C provides an interface for defining continuous piece-wise linear |
33
|
|
|
|
|
|
|
functions by setting knots with x,y pairs. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
use Math::CPWLF; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
$func = Math::CPWLF->new; |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
$func->knot( 0 => 0 ); ## set the knot at f(0) equal to 0 |
40
|
|
|
|
|
|
|
$func->knot( 1 => 2 ); ## set the knot at f(1) equal to 2 |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
$y = $func->( 0.5 ); ## interpolate f(0.5) ($y == 1) |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
Functions can be used in multiple dimensions, by specifying a C |
45
|
|
|
|
|
|
|
object as the y value of a knot. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
$nested_func = Math::CPWLF->new; |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
$nested_func->knot( 0 => 0 ); |
50
|
|
|
|
|
|
|
$nested_func->knot( 1 => 3 ); |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
$func->knot( 2 => $nested_func ); |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
$deep_y = $func->( 1.5 )( 0.5 ); ## $deep_y == 1.75 |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
As a convenience, you can specify arbitrarily deep knots by passing more than |
57
|
|
|
|
|
|
|
two values two the C method. |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
$func->knot( 2, 2 => 4 ); ## same as $nested_func->( 2 => 4); |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
If any of the intermediate knots do not exist they will be autovivified as |
62
|
|
|
|
|
|
|
C objects, much like perl hashes. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
$func->knot( 3, 2 => 4 ); ## autovivify top level f(3) |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=head1 FUNCTIONS |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head2 new |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Construct a new C function with no knots, and the default out of |
71
|
|
|
|
|
|
|
bounds behavior. |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
my $func = Math::CPWLF->new; |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Optional parameters: |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=over 4 |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=item * oob |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
The C parameter controls how a function behaves when a given x value is out |
82
|
|
|
|
|
|
|
of bounds of the current minimum and maximum knots. If a function defines an |
83
|
|
|
|
|
|
|
C method in its constructor, that method is also used for any nested |
84
|
|
|
|
|
|
|
functions that were not explicitly constructed with their own C methods. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=over 4 |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=item * C - Throw an exception (default). |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=item * C - Perform a linear extrapolation using the two nearest knots. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=item * C - Return the y value of the nearest knot. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=item * C - Return undef. |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=back |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
Construct an instance that returns C or empty list when the requested x |
99
|
|
|
|
|
|
|
is out of bounds: |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
my $func = Math::CPWLF->new( oob => 'undef' ); |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=back |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=cut |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub new |
108
|
|
|
|
|
|
|
{ |
109
|
494
|
|
|
494
|
1
|
8451
|
my $self = bless {}, shift(); |
110
|
494
|
|
|
|
|
850
|
my %opts = @_; |
111
|
494
|
|
|
|
|
1235
|
$self->{_opts} = \%opts; |
112
|
494
|
|
|
|
|
1304
|
return $self; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=head2 knot |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
This instance method adds a knot with the given x,y values. |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
$func->knot( $x => $y ); |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Knots can be specified at arbitrary depth and intermediate knots will autovivify |
122
|
|
|
|
|
|
|
as needed. There are two alternate syntaxes for setting deep knots. The first |
123
|
|
|
|
|
|
|
involves passing 3 or more values to the C call, where the last value |
124
|
|
|
|
|
|
|
is the y value and the other values are the depth-ordered x values: |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
$func->knot( $x1, $x2, $x3 => $y ); |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
The other syntax is a bit more hash-like in that it separates the x values. Note |
129
|
|
|
|
|
|
|
that it starts with invoking the C method with no arguments. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
$func->knot->($x1)($x2)( $x3 => $y ); |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=cut |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub knot |
136
|
|
|
|
|
|
|
{ |
137
|
6686
|
|
|
6686
|
1
|
18297
|
my $self = shift @_; |
138
|
|
|
|
|
|
|
|
139
|
6686
|
|
|
|
|
9025
|
delete $self->{_x_vals_order}; |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
## caller intends to use hash-like multi-dimensional syntax |
142
|
|
|
|
|
|
|
## $f->knot->(1)(2)( 3 => 4 ); |
143
|
6686
|
100
|
|
|
|
21926
|
if ( @_ == 0 ) |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
144
|
|
|
|
|
|
|
{ |
145
|
|
|
|
|
|
|
return sub |
146
|
|
|
|
|
|
|
{ |
147
|
627
|
|
|
627
|
|
1265
|
$self->knot( @_ ); |
148
|
627
|
|
|
|
|
2295
|
}; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
## caller is in the middle of using hash-like multi-dimensional syntax |
151
|
|
|
|
|
|
|
elsif ( @_ == 1 ) |
152
|
|
|
|
|
|
|
{ |
153
|
1877
|
|
|
|
|
2035
|
my $x = shift; |
154
|
|
|
|
|
|
|
|
155
|
1877
|
100
|
66
|
|
|
8010
|
if ( ! defined $self->{_data}{$x} || |
156
|
|
|
|
|
|
|
! ref $self->{_data}{$x} ) |
157
|
|
|
|
|
|
|
{ |
158
|
155
|
|
|
|
|
386
|
$self->{_data}{$x} = ( ref $self )->new; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
return sub |
162
|
|
|
|
|
|
|
{ |
163
|
1877
|
|
|
1877
|
|
4531
|
$self->{_data}{$x}->knot( @_ ); |
164
|
1877
|
|
|
|
|
7490
|
}; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
## args are an x,y pair |
167
|
|
|
|
|
|
|
elsif ( @_ == 2 ) |
168
|
|
|
|
|
|
|
{ |
169
|
2285
|
|
|
|
|
2477
|
my ( $x, $y ) = @_; |
170
|
2285
|
|
|
|
|
2395
|
$x += 0; |
171
|
2285
|
|
|
|
|
5201
|
$self->{_data}{$x} = $y; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
## caller is using bulk multi-dimensional syntax |
174
|
|
|
|
|
|
|
## $f->knot( 1, 2, 3 => 4 ); |
175
|
|
|
|
|
|
|
elsif ( @_ > 2 ) |
176
|
|
|
|
|
|
|
{ |
177
|
1897
|
|
|
|
|
2164
|
my $x = shift; |
178
|
|
|
|
|
|
|
|
179
|
1897
|
|
|
|
|
1792
|
$x += 0; |
180
|
|
|
|
|
|
|
|
181
|
1897
|
100
|
66
|
|
|
7548
|
if ( ! defined $self->{_data}{$x} || ! ref $self->{_data}{$x} ) |
182
|
|
|
|
|
|
|
{ |
183
|
163
|
|
|
|
|
402
|
$self->{_data}{$x} = ( ref $self )->new; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
1897
|
|
|
|
|
4454
|
$self->{_data}{$x}->knot(@_); |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
4182
|
|
|
|
|
8130
|
return $self; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
## - solves the first dimension lookup, or |
194
|
|
|
|
|
|
|
## - returns first dimension closure as needed |
195
|
|
|
|
|
|
|
sub _top_interp_closure |
196
|
|
|
|
|
|
|
{ |
197
|
473
|
|
|
473
|
|
676
|
my ( $func, $opts ) = @_; |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
my $interp = sub |
200
|
|
|
|
|
|
|
{ |
201
|
473
|
|
|
473
|
|
672
|
my ( $x_given ) = @_; |
202
|
|
|
|
|
|
|
|
203
|
473
|
|
|
|
|
659
|
$x_given += 0; |
204
|
|
|
|
|
|
|
|
205
|
473
|
|
|
|
|
1142
|
my $node = $func->_make_node($x_given, $opts); |
206
|
|
|
|
|
|
|
|
207
|
465
|
100
|
|
|
|
1360
|
return _nada() if ! defined $node; |
208
|
|
|
|
|
|
|
|
209
|
462
|
|
|
|
|
861
|
my @slice = ( $node ); |
210
|
462
|
|
|
|
|
1510
|
my @tree = ( \@slice ); |
211
|
|
|
|
|
|
|
|
212
|
462
|
100
|
100
|
|
|
2824
|
return ref $node->{y_dn} || ref $node->{y_up} |
213
|
|
|
|
|
|
|
? _nested_interp_closure( \@tree, $opts ) |
214
|
|
|
|
|
|
|
: _reduce_tree( \@tree ) |
215
|
473
|
|
|
|
|
3012
|
}; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
## - solves the 2+ dimension lookups, or |
220
|
|
|
|
|
|
|
## - returns 2+ dimension closures as needed |
221
|
|
|
|
|
|
|
sub _nested_interp_closure |
222
|
|
|
|
|
|
|
{ |
223
|
76
|
|
|
76
|
|
106
|
my ( $tree, $opts ) = @_; |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
my $interp = sub |
226
|
|
|
|
|
|
|
{ |
227
|
67
|
|
|
67
|
|
81
|
my ($x_given) = @_; |
228
|
|
|
|
|
|
|
|
229
|
67
|
|
|
|
|
73
|
$x_given += 0; |
230
|
|
|
|
|
|
|
|
231
|
67
|
|
|
|
|
67
|
my @slice; |
232
|
|
|
|
|
|
|
my $make_closure; |
233
|
|
|
|
|
|
|
|
234
|
67
|
|
|
|
|
72
|
for my $node ( @{ $tree->[-1] } ) |
|
67
|
|
|
|
|
122
|
|
235
|
|
|
|
|
|
|
{ |
236
|
|
|
|
|
|
|
|
237
|
120
|
|
|
|
|
154
|
for my $y_pos ( 'y_dn', 'y_up' ) |
238
|
|
|
|
|
|
|
{ |
239
|
|
|
|
|
|
|
|
240
|
236
|
100
|
|
|
|
534
|
next if ! ref $node->{$y_pos}; |
241
|
|
|
|
|
|
|
|
242
|
229
|
|
|
|
|
594
|
my $new_node = $node->{$y_pos}->_make_node($x_given, $opts); |
243
|
|
|
|
|
|
|
|
244
|
227
|
100
|
|
|
|
461
|
return _nada() if ! defined $new_node; |
245
|
|
|
|
|
|
|
|
246
|
225
|
|
100
|
|
|
718
|
$make_closure = ref $new_node->{y_dn} || ref $new_node->{y_up}; |
247
|
|
|
|
|
|
|
|
248
|
225
|
|
|
|
|
458
|
$new_node->{into} = \$node->{$y_pos}; |
249
|
|
|
|
|
|
|
|
250
|
225
|
|
|
|
|
588
|
push @slice, $new_node; |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
63
|
|
|
|
|
82
|
push @{ $tree }, \@slice; |
|
63
|
|
|
|
|
96
|
|
257
|
|
|
|
|
|
|
|
258
|
63
|
100
|
|
|
|
155
|
return $make_closure ? _nested_interp_closure( $tree, $opts ) |
259
|
|
|
|
|
|
|
: _reduce_tree( $tree ) |
260
|
|
|
|
|
|
|
|
261
|
76
|
|
|
|
|
303
|
}; |
262
|
|
|
|
|
|
|
|
263
|
76
|
|
|
|
|
320
|
return $interp; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
## converts the final tree of curried line segments and x values to the final |
267
|
|
|
|
|
|
|
## y value |
268
|
|
|
|
|
|
|
sub _reduce_tree |
269
|
|
|
|
|
|
|
{ |
270
|
449
|
|
|
449
|
|
1279
|
my ($tree) = @_; |
271
|
|
|
|
|
|
|
|
272
|
449
|
|
|
|
|
540
|
for my $slice ( reverse @{ $tree } ) |
|
449
|
|
|
|
|
822
|
|
273
|
|
|
|
|
|
|
{ |
274
|
|
|
|
|
|
|
|
275
|
503
|
|
|
|
|
903
|
for my $node ( @{ $slice } ) |
|
503
|
|
|
|
|
923
|
|
276
|
|
|
|
|
|
|
{ |
277
|
|
|
|
|
|
|
|
278
|
650
|
|
|
|
|
770
|
my @line = grep defined, @{ $node }{ qw/ x_dn x_up y_dn y_up / }; |
|
650
|
|
|
|
|
3293
|
|
279
|
|
|
|
|
|
|
|
280
|
650
|
|
|
|
|
1536
|
my $y_given = _mx_plus_b( $node->{x_given}, @line ); |
281
|
|
|
|
|
|
|
|
282
|
650
|
100
|
|
|
|
5924
|
return $y_given if ! $node->{into}; |
283
|
|
|
|
|
|
|
|
284
|
201
|
|
|
|
|
187
|
${ $node->{into} } = $y_given; |
|
201
|
|
|
|
|
522
|
|
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
## used to handle 'undef' oob exceptions |
293
|
|
|
|
|
|
|
## - returns a reference to itself in CODEREF context |
294
|
|
|
|
|
|
|
## - else returns undef |
295
|
|
|
|
|
|
|
sub _nada |
296
|
|
|
|
|
|
|
{ |
297
|
6
|
100
|
|
6
|
|
73
|
return want('CODE') ? \&_nada : (); |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
{ |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
my $default_opts = |
303
|
|
|
|
|
|
|
{ |
304
|
|
|
|
|
|
|
oob => 'die', |
305
|
|
|
|
|
|
|
}; |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
## - merges the options, priority from high to low is: |
308
|
|
|
|
|
|
|
## - object |
309
|
|
|
|
|
|
|
## - inherited |
310
|
|
|
|
|
|
|
## - defaults |
311
|
|
|
|
|
|
|
sub _merge_opts |
312
|
|
|
|
|
|
|
{ |
313
|
34
|
|
|
34
|
|
568
|
my ($self, $inherited_opts) = @_; |
314
|
|
|
|
|
|
|
|
315
|
34
|
|
|
|
|
30
|
my %opts; |
316
|
|
|
|
|
|
|
|
317
|
34
|
|
|
|
|
62
|
for my $opts ( $self->{_opts}, $inherited_opts, $default_opts ) |
318
|
|
|
|
|
|
|
{ |
319
|
102
|
|
|
|
|
102
|
for my $opt ( keys %{ $opts } ) |
|
102
|
|
|
|
|
184
|
|
320
|
|
|
|
|
|
|
{ |
321
|
85
|
100
|
|
|
|
219
|
next if defined $opts{$opt}; |
322
|
34
|
|
|
|
|
111
|
$opts{$opt} = $opts->{$opt}; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
34
|
|
|
|
|
70
|
return \%opts; |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
## - locate the neighboring x and y pairs to the given x values |
332
|
|
|
|
|
|
|
## - handles oob exceptions |
333
|
|
|
|
|
|
|
## - handles direct hits |
334
|
|
|
|
|
|
|
## - handles empty functions |
335
|
|
|
|
|
|
|
sub _make_node |
336
|
|
|
|
|
|
|
{ |
337
|
702
|
|
|
702
|
|
1029
|
my ($self, $x, $opts) = @_; |
338
|
|
|
|
|
|
|
|
339
|
702
|
100
|
|
|
|
2175
|
if ( ! exists $self->{_x_vals_order} ) |
340
|
|
|
|
|
|
|
{ |
341
|
118
|
|
|
|
|
240
|
$self->_order_x_vals; |
342
|
118
|
|
|
|
|
238
|
$self->_index_x_vals; |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
702
|
100
|
|
|
|
773
|
if ( ! @{ $self->{_x_vals_order} } ) |
|
702
|
|
|
|
|
1874
|
|
346
|
|
|
|
|
|
|
{ |
347
|
1
|
|
|
|
|
9
|
die "Error: cannot interpolate with no knots"; |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
701
|
|
|
|
|
870
|
my ( $x_dn_i, $x_up_i, $oob ); |
351
|
|
|
|
|
|
|
|
352
|
701
|
100
|
|
|
|
5311
|
if ( exists $self->{_x_vals_index}{$x} ) |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
353
|
|
|
|
|
|
|
{ |
354
|
359
|
|
|
|
|
642
|
$x_dn_i = $self->{_x_vals_index}{$x}; |
355
|
359
|
|
|
|
|
492
|
$x_up_i = $x_dn_i; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
elsif ( $x < $self->{_x_vals_order}[0] ) |
358
|
|
|
|
|
|
|
{ |
359
|
5
|
|
|
|
|
9
|
$x_dn_i = 0; |
360
|
5
|
|
|
|
|
5
|
$x_up_i = 0; |
361
|
5
|
|
|
|
|
6
|
$oob = 1; |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
elsif ( $x > $self->{_x_vals_order}[-1] ) |
364
|
|
|
|
|
|
|
{ |
365
|
29
|
|
|
|
|
52
|
$x_dn_i = -1; |
366
|
29
|
|
|
|
|
29
|
$x_up_i = -1; |
367
|
29
|
|
|
|
|
32
|
$oob = 1; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
else |
370
|
|
|
|
|
|
|
{ |
371
|
|
|
|
|
|
|
( $x_dn_i, $x_up_i ) = do |
372
|
308
|
|
|
|
|
372
|
{ |
373
|
308
|
|
|
|
|
474
|
my $min = 0; |
374
|
308
|
|
|
|
|
357
|
my $max = $#{ $self->{_x_vals_order} }; |
|
308
|
|
|
|
|
557
|
|
375
|
308
|
|
|
|
|
790
|
_binary_search( $self->{_x_vals_order}, $x, $min, $max ); |
376
|
|
|
|
|
|
|
}; |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
701
|
100
|
|
|
|
1799
|
if ( $oob ) |
380
|
|
|
|
|
|
|
{ |
381
|
34
|
|
|
|
|
62
|
my $merge_opts = $self->_merge_opts( $opts ); |
382
|
34
|
100
|
|
|
|
118
|
if ( $merge_opts->{oob} eq 'die' ) |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
383
|
|
|
|
|
|
|
{ |
384
|
8
|
|
|
|
|
1015
|
Carp::confess "Error: given X ($x) was out of bounds of" |
385
|
|
|
|
|
|
|
. " function min or max"; |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
elsif ( $merge_opts->{oob} eq 'extrapolate' ) |
388
|
|
|
|
|
|
|
{ |
389
|
7
|
100
|
|
|
|
40
|
if ( $x < $self->{_x_vals_order}[0] ) |
|
|
50
|
|
|
|
|
|
390
|
|
|
|
|
|
|
{ |
391
|
1
|
|
|
|
|
2
|
$x_up_i = List::Util::min( $#{ $self->{_x_vals_order} }, $x_up_i + 1 ); |
|
1
|
|
|
|
|
7
|
|
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
elsif ( $x > $self->{_x_vals_order}[-1] ) |
394
|
|
|
|
|
|
|
{ |
395
|
6
|
|
|
|
|
27
|
$x_dn_i = List::Util::max( 0, $x_dn_i - 1 ); |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
elsif ( $merge_opts->{oob} eq 'level' ) |
399
|
|
|
|
|
|
|
{ |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
elsif ( $merge_opts->{oob} eq 'undef' ) |
402
|
|
|
|
|
|
|
{ |
403
|
5
|
|
|
|
|
12
|
return; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
else |
406
|
|
|
|
|
|
|
{ |
407
|
1
|
|
|
|
|
182
|
Carp::confess "Error: invalid oob option ($merge_opts->{oob})"; |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
687
|
|
|
|
|
1438
|
my $x_dn = $self->{_x_vals_order}[ $x_dn_i ]; |
412
|
687
|
|
|
|
|
1167
|
my $x_up = $self->{_x_vals_order}[ $x_up_i ]; |
413
|
|
|
|
|
|
|
|
414
|
687
|
|
|
|
|
1526
|
my $y_dn = $self->{_data}{$x_dn}; |
415
|
687
|
|
|
|
|
1487
|
my $y_up = $self->{_data}{$x_up}; |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
return |
418
|
|
|
|
|
|
|
{ |
419
|
687
|
|
|
|
|
3968
|
x_given => $x, |
420
|
|
|
|
|
|
|
x_dn => $x_dn, |
421
|
|
|
|
|
|
|
x_up => $x_up, |
422
|
|
|
|
|
|
|
y_dn => $y_dn, |
423
|
|
|
|
|
|
|
y_up => $y_up, |
424
|
|
|
|
|
|
|
}; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
## converts a given x value and two points that define a line |
428
|
|
|
|
|
|
|
## to the corresponding y value |
429
|
|
|
|
|
|
|
sub _mx_plus_b |
430
|
|
|
|
|
|
|
{ |
431
|
650
|
|
|
650
|
|
1362
|
my ( $x, $x_dn, $x_up, $y_dn, $y_up ) = @_; |
432
|
|
|
|
|
|
|
|
433
|
650
|
100
|
|
|
|
1407
|
if ( $y_dn == $y_up ) |
434
|
|
|
|
|
|
|
{ |
435
|
385
|
|
|
|
|
727
|
return $y_dn; |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
|
438
|
265
|
|
|
|
|
441
|
my $slope = ( $y_up - $y_dn ) / ( $x_up - $x_dn ); |
439
|
265
|
|
|
|
|
425
|
my $intercept = $y_up - ( $slope * $x_up ); |
440
|
265
|
|
|
|
|
438
|
my $y = $slope * $x + $intercept; |
441
|
|
|
|
|
|
|
|
442
|
265
|
|
|
|
|
701
|
return $y; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
## vanilla binary search algorithm used to locate a given x value |
446
|
|
|
|
|
|
|
## that is within the defined range of the function |
447
|
|
|
|
|
|
|
sub _binary_search |
448
|
|
|
|
|
|
|
{ |
449
|
324
|
|
|
324
|
|
11657
|
my ( $array, $value, $min_index, $max_index ) = @_; |
450
|
|
|
|
|
|
|
|
451
|
324
|
|
|
|
|
433
|
my $range_max_index = $max_index - $min_index; |
452
|
|
|
|
|
|
|
|
453
|
324
|
|
|
|
|
808
|
while ( $range_max_index > 1 ) |
454
|
|
|
|
|
|
|
{ |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
## size: 3 4 5 6 7 8 9 10 20 |
457
|
|
|
|
|
|
|
## mid: 1 2 2 3 3 4 4 5 10 |
458
|
1743
|
|
|
|
|
2770
|
my $mid_index = $min_index + int( $range_max_index / 2 ); |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
## value is inside the upper half |
461
|
1743
|
100
|
|
|
|
3261
|
if ( $value > $array->[$mid_index] ) |
462
|
|
|
|
|
|
|
{ |
463
|
888
|
|
|
|
|
1039
|
$min_index = $mid_index; |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
## value is inside the lower half |
466
|
|
|
|
|
|
|
else |
467
|
|
|
|
|
|
|
{ |
468
|
855
|
|
|
|
|
1007
|
$max_index = $mid_index; |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
|
471
|
1743
|
|
|
|
|
3665
|
$range_max_index = $max_index - $min_index; |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
} |
474
|
|
|
|
|
|
|
|
475
|
324
|
100
|
|
|
|
1141
|
return $range_max_index > -1 |
476
|
|
|
|
|
|
|
? ( $min_index, $max_index ) |
477
|
|
|
|
|
|
|
: ( undef, undef ); |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
## - called on the first lookup after a knot has been set |
482
|
|
|
|
|
|
|
## - caches an array of ordered x values |
483
|
|
|
|
|
|
|
sub _order_x_vals |
484
|
|
|
|
|
|
|
{ |
485
|
118
|
|
|
118
|
|
139
|
my ( $self ) = @_; |
486
|
|
|
|
|
|
|
|
487
|
118
|
|
|
|
|
136
|
my @ordered_x_vals = sort { $a <=> $b } keys %{ $self->{_data} }; |
|
1905
|
|
|
|
|
2162
|
|
|
118
|
|
|
|
|
706
|
|
488
|
|
|
|
|
|
|
|
489
|
118
|
|
|
|
|
359
|
$self->{_x_vals_order} = \@ordered_x_vals; |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
## - called on the first lookup after a knot has been set |
493
|
|
|
|
|
|
|
## - creates an index mapping knot x values to their ordered indexes |
494
|
|
|
|
|
|
|
sub _index_x_vals |
495
|
|
|
|
|
|
|
{ |
496
|
118
|
|
|
118
|
|
148
|
my ( $self ) = @_; |
497
|
|
|
|
|
|
|
|
498
|
118
|
|
|
|
|
186
|
delete $self->{_x_vals_index}; |
499
|
118
|
|
|
|
|
127
|
for my $i ( 0 .. $#{ $self->{_x_vals_order} } ) |
|
118
|
|
|
|
|
320
|
|
500
|
|
|
|
|
|
|
{ |
501
|
669
|
|
|
|
|
1561
|
$self->{_x_vals_index}{ $self->{_x_vals_order}[$i] } = $i; |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=head1 AUTHOR |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
Dan Boorstein, C<< >> |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=head1 BUGS |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
512
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
513
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
=head1 SUPPORT |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
perldoc Math::CPWLF |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
You can also look for information at: |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=over 4 |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
L |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
L |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
=item * CPAN Ratings |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
L |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
=item * Search CPAN |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
L |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
=back |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
Copyright 2009 Dan Boorstein. |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
553
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
554
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
See http://dev.perl.org/licenses/ for more information. |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=cut |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
1; # End of Math::CPWLF |