File Coverage

blib/lib/Math/Series.pm
Criterion Covered Total %
statement 114 127 89.7
branch 37 50 74.0
condition 13 21 61.9
subroutine 12 12 100.0
pod 5 5 100.0
total 181 215 84.1


line stmt bran cond sub pod time code
1            
2             =head1 NAME
3            
4             Math::Series - Perl extension dealing with mathematic series
5            
6             =head1 SYNOPSIS
7            
8             use Math::Series;
9             my $x_n = Math::Series->new( formula => 'n*x',
10             start_value => 1,
11             iteration_var => 'n',
12             previous_var => 'x',
13             start_index => 0,
14             cached => 1 );
15            
16             print $x_n->next(), "\n" foreach 0..5;
17             # prints 1, 2, 6, 24...
18            
19             print $x_n->at_index(3);
20             # prints 24
21            
22             =head1 DESCRIPTION
23            
24             Math::Series defines a class for simple mathematic series with a
25             recursive definition such as C. Such a recursive
26             definition is treated as a sequence whose elements will be added to form
27             a series. You can refer to the previous sequence element as well as to the
28             current index in the series. Creation of a
29             Math::Series object is described below in the paragraph about the
30             constructor.
31            
32             Math::Series uses Math::Symbolic to parse and modify the recursive
33             sequence definitions. That means you specify the sequence as a string which
34             is parsed by Math::Symbolic. Alternatively, you can pass the constructor
35             a Math::Symbolic tree directly.
36            
37             Because Math::Series uses Math::Symbolic for its implementation, all results
38             will be Math::Symbolic objects which may contain other variables than the
39             sequence variable and the iterator variable.
40            
41             Each Math::Series object is an iterator to iterate over the elements of the
42             series starting at the first element (which was specified by the starting
43             element, the second argument to the new() constructor). It offers
44             facilities to cache all calculated elements and access any element directly,
45             though unless the element has been cached in a previous calculation, this
46             is just a shortcut for repeated use of the iterator.
47            
48             Every element in the series may only access its predecessor, not the elements
49             before that.
50            
51             =cut
52            
53             package Math::Series;
54            
55 1     1   997 use 5.006;
  1         4  
  1         42  
56 1     1   6 use strict;
  1         1  
  1         38  
57 1     1   20 use warnings;
  1         2  
  1         45  
58            
59             our $VERSION = '1.01';
60            
61 1     1   5 use Carp;
  1         1  
  1         79  
62            
63 1     1   859 use Math::Symbolic qw/:all/;
  1         149181  
  1         300  
64 1     1   1055 use Math::Sequence;
  1         2298  
  1         36  
65 1     1   7 use base 'Math::Sequence';
  1         2  
  1         1196  
66            
67             =head2 CLASS DATA
68            
69             Math::Series defines the following package variables:
70            
71             =over 2
72            
73             =item $Math::Series::Parser
74            
75             This scalar contains a Parse::RecDescent parser to parse formulas.
76             It is derived from the Math::Symbolic::Parser grammar.
77            
78             =cut
79            
80             our $Parser = Math::Symbolic::Parser->new();
81            
82             #$Parser->Extend(<<'GRAMMAR');
83             #GRAMMAR
84            
85             =item $Math::Series::warnings
86            
87             This scalar indicates whether Math::Series should warn about the performance
88             implications of using the back() method on uncached series. It defaults
89             to true.
90            
91             =cut
92            
93             our $warnings = 1;
94            
95             =pod
96            
97             =back
98            
99             =head2 METHODS
100            
101             =over 2
102            
103             =item new()
104            
105             The constructor for Math::Series objects. It takes named parameters. The
106             following parameters are required:
107            
108             =over 2
109            
110             =item formula
111            
112             The formula is the recursive definition of a sequence whose elements up to
113             the current element will be summed to form the current element of the series.
114             The formula may contain various Math::Symbolic variables that are assigned
115             a value elsewhere in your code, but it may also contain two special variables:
116             The number of the current iteration step, starting with 0, and the previous
117             element of the series.
118            
119             The formula may be specified as a string that can be parsed by a Math::Symbolic
120             parser or as a Math::Symbolic tree directly. Please refer to the Math::Symbolic
121             and Math::Symbolic::Parser man pages for details.
122            
123             =item start_value
124            
125             This parameter defines the starting value for the series. It used as the
126             element in the series that is defined as the lowest series element by the
127             start_index parameter.
128             The starting value may be a string that can be parsed as a valid
129             Math::Symbolic tree or a preconstructed Math::Symbolic tree.
130            
131             =back
132            
133             The following parameters are optional:
134            
135             =over 2
136            
137             =item iteration_var
138            
139             The iteration variable is the name of the variable in the Math::Symbolic tree
140             that refers to the current iteration step. It defaults to the variable 'n'.
141            
142             It must be a valid Math::Symbolic variable identifier. (That means it is
143             C.)
144            
145             =item previous_var
146            
147             The previous_var parameter sets the name of the variable that represents the
148             previous iteration step. It defaults to the name 'x' and must be a valid
149             Math::Symbolic variable identifier just like the iteration variable.
150            
151             =item cached
152            
153             This parameter indicates whether or not to cache the calculated series'
154             elements for faster direct access. It defaults to true. At run-time,
155             the caching behaviour may be altered using the cached() method.
156            
157             =item start_index
158            
159             The lower boundary for the series' summation. It defaults to 0, but may be
160             set to any positive integer or zero.
161            
162             =back
163            
164             =cut
165            
166             sub new {
167 18     18 1 16476 my $proto = shift;
168 18   33     108 my $class = ref($proto) || $proto;
169            
170 18 50       82 croak "Invalid number of arguments to Math::Series->new()."
171             if @_ % 2;
172            
173 18         71 my %args = @_;
174 18         58 my $formula = $args{formula};
175            
176 18 50       51 croak "Math::Series->new() requires a formula parameter."
177             if not defined $formula;
178            
179 18         146 my $parsed = $Parser->parse($formula);
180 18 50       98146 croak "Error parsing formula." if not defined $parsed;
181            
182 18         45 my $start = $args{start_value};
183 18 50       55 croak "A starting value must be supplied to Math::Series->new() as\n"
184             . "second argument."
185             if not defined $start;
186 18         121 $start = $Parser->parse($start);
187 18 50       20062 croak "Error parsing starting value." if not defined $start;
188            
189 18         51 my $variable = $args{previous_var};
190 18 100       55 $variable = 'x' if not defined $variable;
191            
192 18         46 my $iter_var = $args{iteration_var};
193 18 100       52 $iter_var = 'n' if not defined $iter_var;
194            
195 18         32 my $cached = 1;
196 18 100       64 $cached = $args{cached} if exists $args{cached};
197            
198 18         37 my $start_index = $args{start_index};
199            
200 18 100       45 $start_index = 0 if not defined $start_index;
201            
202 18         167 my $self = {
203             cached => $cached,
204             var => $variable,
205             formula => $parsed,
206             current => $start_index,
207             current_value => $start,
208             cache => [$start],
209             iter_var => $iter_var,
210             start_index => $start_index,
211             };
212 18         132 return bless $self => $class;
213             }
214            
215             =item next()
216            
217             The next() method returns the next element of the series and advances the
218             iterator by one. This is the prefered method of walking down a series'
219             recursion.
220            
221             =cut
222            
223             sub next {
224 372     372 1 25022 my $self = shift;
225 372         699 my $current_index = $self->{current};
226 372         706 my $current_value = $self->{current_value};
227 372         699 my $next_index = $current_index + 1;
228 372         523 my $start_index = $self->{start_index};
229            
230 372 100 100     1360 if ( $self->{cached}
231             and defined $self->{cache}[ $next_index - $start_index ] )
232             {
233 18         40 $self->{current_value} = $self->{cache}[ $next_index - $start_index ];
234 18         25 $self->{current} = $next_index;
235 18         56 return $current_value;
236             }
237            
238 354         1039 my $next_value = $current_value->new();
239 354         9156 my $add = $self->{formula}->new();
240 354         22256 $add->implement(
241             $self->{var} => $current_value,
242             $self->{iter_var} => $current_index + 1
243             );
244 354         539464 $add = $add->simplify();
245 354         164269 $next_value += $add;
246 354         14644 $next_value = $next_value->simplify();
247            
248 354 100       143327 $self->{cache}[ $next_index - $start_index ] = $next_value
249             if $self->{cached};
250 354         650 $self->{current} = $next_index;
251 354         816 $self->{current_value} = $next_value;
252            
253 354         1721 return $current_value;
254             }
255            
256             =item cached()
257            
258             Returns a true value if the series is currently being cached, false if it
259             isn't. By default, new objects have caching enabled. It is suggested that you
260             only disable caching if space is an issue and you will only walk the series
261             uni-directionally and only once.
262            
263             cached() can be used to change the caching behaviour. If the first argument is
264             true, caching will be enabled. If it is false, caching will be disabled.
265            
266             =cut
267            
268             # cached inherited.
269            
270             =item current_index()
271            
272             Returns the index of the current element. That is, the index of the element
273             that will be returned by the next call to the next() method.
274            
275             This method also allows (re-)setting the element that will be next returned by
276             the next() method. In that case, the first argument shoudl be the appropriate
277             index.
278            
279             Returns undef and doesn't set the current index if the argument is below 0.
280            
281             =cut
282            
283             sub current_index {
284 234     234 1 69452 my $self = shift;
285 234 100 66     991 if ( @_ and defined $_[0] ) {
286 42         688 my $index = shift;
287 42 100       157 return undef if $index < $self->{start_index};
288 24         238 $self->{current_value} = $self->at_index($index);
289 24         96 $self->{current} = $index;
290 24         296 return $index;
291             }
292             else {
293 192         764 return $self->{current};
294             }
295             }
296            
297             =item at_index()
298            
299             This method returns the series element with the index denoted by the first
300             argument to the method. It does not change the state of the iterator.
301             This method is extremely slow for uncached series.
302            
303             Returns undef for indices below the starting index.
304            
305             =cut
306            
307             sub at_index {
308 120     120 1 19527 my $self = shift;
309 120         168 my $index = shift;
310 120 50       336 croak "Math::Series->at_index() takes an index as argument."
311             if not defined $index;
312 120         264 my $start_index = $self->{start_index};
313 120 100       335 return undef if $index < $start_index;
314            
315 108 100 100     839 return $self->{cache}[ $index - $start_index ]
316             if $self->{cached}
317             and defined $self->{cache}[ $index - $start_index ];
318            
319 84 100       258 if ( $self->{cached} ) {
320 6 50       15 if ( $index - $start_index > $#{ $self->{cache} } ) {
  6         29  
321 6         16 my $old_index = $self->{current};
322 6         44 $self->next() for 1 .. $index - $self->{current};
323 6         14 my $value = $self->{current_value};
324 6         15 $self->{current} = $old_index;
325 6         18 $self->{current_value} =
326             $self->{cache}[ $old_index - $start_index ];
327 6         26 return $value;
328             }
329             else {
330 0 0       0 return $self->{cache}[ $index - $start_index ]
331             if defined $self->{cache}[ $index - $start_index ];
332 0         0 my $last_defined = $index;
333 0   0     0 while ( not defined $self->{cache}[ $last_defined - $start_index ]
334             and $last_defined >= $start_index )
335             {
336 0         0 $last_defined--;
337             }
338 0 0       0 die "Sanity check!" if $last_defined < $start_index;
339 0         0 my $old_index = $self->{current};
340 0         0 $self->{current} = $last_defined;
341 0         0 $self->{current_value} =
342             $self->{cache}[ $last_defined - $start_index ];
343 0         0 $self->next() for 1 .. $index - $last_defined;
344 0         0 my $value = $self->{current_value};
345 0         0 $self->{current} = $old_index;
346 0         0 $self->{current_value} =
347             $self->{cache}[ $old_index - $start_index ];
348 0         0 return $value;
349             }
350             }
351             else { # not $self->{cached}
352 78 50       218 return $self->{current_value} if $index == $self->{current};
353 78         141 my $old_index = $self->{current};
354 78         114 my $old_value = $self->{current_value};
355 78         111 my $value;
356 78 100       219 if ( $index < $self->{current} ) {
357 60         104 $self->{current} = $start_index;
358 60         153 $self->{current_value} = $self->{cache}[0];
359 60         292 $self->next() for $start_index .. $index - 1;
360 60         302 $value = $self->{current_value};
361             }
362             else {
363 18         110 $self->next() for 1 .. $index - $old_index;
364 18         56 $value = $self->{current_value};
365             }
366 78         206 $self->{current} = $old_index;
367 78         190 $self->{current_value} = $old_value;
368 78         293 return $value;
369             }
370             }
371            
372             =item back()
373            
374             This methods returns the series element previously returned by the next()
375             method. Since it is extremely slow on uncached series, it warns about this
376             performance hit by default. To turn this warning off, set the
377             $Math::Series::warnings scalar to a false value.
378            
379             This method decrements the current iterator series element.
380            
381             Returns undef if the current index goes below the starting index.
382            
383             =cut
384            
385             sub back {
386 108     108 1 40054 my $self = shift;
387 108         241 my $current_index = $self->{current};
388 108         202 my $current_value = $self->{current_value};
389 108         174 my $prev_index = $current_index - 1;
390 108         177 my $start_index = $self->{start_index};
391 108 100       303 return undef if $prev_index < $start_index;
392            
393 96 50 66     386 carp "Use of the back() method on uncached series is not advised."
394             if ( not $self->{cached} )
395             and $Math::Series::warnings;
396            
397 96 100 66     507 if ( $self->{cached}
398             and defined $self->{cache}[ $prev_index - $start_index ] )
399             {
400 48         128 $self->{current_value} = $self->{cache}[ $prev_index - $start_index ];
401 48         96 $self->{current} = $prev_index;
402 48         251 return $self->{current_value};
403             }
404            
405 48         148 my $prev_value = $self->at_index($prev_index);
406 48         98 $self->{current} = $prev_index;
407 48         106 $self->{current_value} = $prev_value;
408            
409 48         248 return $prev_value;
410             }
411            
412             1;
413             __END__