File Coverage

blib/lib/CXC/Number/Sequence/Ratio.pm
Criterion Covered Total %
statement 38 38 100.0
branch n/a
condition n/a
subroutine 13 13 100.0
pod n/a
total 51 51 100.0


line stmt bran cond sub pod time code
1             package CXC::Number::Sequence::Ratio;
2              
3             # ABSTRACT: Numeric Sequence with Relative Fractional Spacing
4              
5 3     3   524597 use strict;
  3         7  
  3         99  
6 3     3   13 use warnings;
  3         6  
  3         148  
7              
8 3     3   37 use v5.28;
  3         9  
9              
10             # ABSTRACT: ratio sequence
11              
12 3     3   1853 use Types::Standard qw( Optional );
  3         409687  
  3         29  
13 3     3   9121 use Types::Common::Numeric qw( PositiveInt );
  3         79840  
  3         31  
14 3     3   7464 use Math::BigInt;
  3         122978  
  3         15  
15 3     3   84452 use List::Util qw( max );
  3         6  
  3         342  
16              
17 3     3   1765 use CXC::Number::Sequence::Failure -all;
  3         14  
  3         38  
18 3     3   2990 use CXC::Number::Sequence::Types -all;
  3         195  
  3         59  
19              
20 3     3   19211 use CXC::Number::Sequence::Utils qw( buildargs_factory );
  3         18  
  3         48  
21              
22 3     3   2564 use enum qw( BITMASK: MIN MAX SOFT_MIN SOFT_MAX NELEM W0 RATIO E0 );
  3         4385  
  3         23  
23              
24             my %ArgMap = (
25             w0 => { type => Spacing, flag => W0 },
26             e0 => { type => Optional [BigNum], flag => E0 },
27             max => { type => Optional [BigNum], flag => MAX },
28             min => { type => Optional [BigNum], flag => MIN },
29             nelem => { type => Optional [PositiveInt], flag => NELEM },
30             ratio => { type => Ratio, flag => RATIO },
31             soft_max => { type => Optional [BigNum], flag => SOFT_MAX },
32             soft_min => { type => Optional [BigNum], flag => SOFT_MIN },
33             );
34              
35 3     3   4816 use Moo;
  3         20226  
  3         17  
36              
37             extends 'CXC::Number::Sequence';
38              
39 3     3   5237 use namespace::clean;
  3         5  
  3         33  
40              
41             our $VERSION = '0.13';
42              
43             my sub nelem {
44             my ( $ratio, $w0, $Dr ) = @_;
45              
46             $w0 = $w0->copy->babs;
47             $Dr = $Dr->copy->babs;
48              
49             return ( ( ( $w0 - ( 1 - $ratio ) * $Dr ) / $w0 )->blog / $ratio->copy->blog )->bceil + 1;
50             }
51              
52             my sub DRn_factory {
53             my ( $w0, $ratio ) = map { $_->copy } @_;
54              
55             $w0 = $w0->copy->babs;
56              
57             return sub {
58             map { $w0 * ( 1 - $ratio->copy->bpow( $_ ) ) / ( 1 - $ratio ) } @_;
59             };
60              
61             }
62              
63             my sub covers_range {
64             my ( $ratio, $w0, $Dr ) = @_;
65              
66             return if $ratio->copy->babs >= 1;
67              
68             $Dr = $Dr->copy->babs;
69             $w0 = $w0->copy->babs;
70              
71             return if $Dr <= $w0 / ( 1 - $ratio );
72              
73             my $min_w0 = $Dr * ( 1 - $ratio );
74             parameter_constraint->throw( "spacing ($w0) is too small to cover range; must be >= $min_w0\n" );
75             }
76              
77             my sub e0_le_min {
78              
79             my ( $ratio, $w0, $min, $max, $E0 ) = @_;
80              
81             # ensure we can get to $max
82             covers_range( $ratio, $w0, $max - $E0 );
83              
84             my $DRn = DRn_factory( $w0, $ratio );
85              
86             # if $min == $E0, $nmin will be -1; it should be 0
87             my $nmin = max( 0, nelem( $ratio, $w0, $min - $E0 ) - 2 );
88             my $nmax = nelem( $ratio, $w0, $max - $E0 ) - 1;
89              
90             return [ map { $E0 + $_ } $DRn->( $nmin .. $nmax ) ];
91             }
92              
93             my sub e0_ge_max {
94              
95             my ( $ratio, $w0, $min, $max, $E0 ) = @_;
96              
97             # ensure we can get to $min
98             covers_range( $ratio, $w0, $E0 - $min );
99              
100             my $DRn = DRn_factory( $w0, $ratio );
101             my $nmin = nelem( $ratio, $w0, $E0 - $min ) - 1;
102              
103             # if $max == $E0, $nmax will be -1; it should be 0
104             my $nmax = max( 0, nelem( $ratio, $w0, $max - $E0 ) - 2 );
105              
106             return [ map { $E0 - $_ } $DRn->( reverse $nmax .. $nmin ) ];
107             }
108              
109             my %ArgBuild;
110             %ArgBuild = (
111             ( MIN | SOFT_MAX | W0 | RATIO ),
112             sub {
113             { elements => e0_le_min( $_->ratio, $_->w0, $_->min, $_->soft_max, $_->min ) };
114             },
115              
116             ( MIN | NELEM | W0 | RATIO ),
117             sub {
118             my $DRn = DRn_factory( $_->w0, $_->ratio );
119             my $E0 = $_->min;
120              
121             { elements => [ map { $E0 + $_ } $DRn->( 0 .. $_->nelem - 1 ) ] };
122             },
123              
124             ( SOFT_MIN | MAX | W0 | RATIO ),
125             sub {
126             { elements => e0_ge_max( $_->ratio, $_->w0, $_->soft_min, $_->max, $_->max ) };
127             },
128              
129              
130             ( MAX | NELEM | W0 | RATIO ),
131             sub {
132              
133             my $DRn = DRn_factory( $_->w0, $_->ratio );
134             my $E0 = $_->max;
135              
136             { elements => [ map { $E0 - $_ } $DRn->( reverse 0 .. $_->nelem - 1 ) ], };
137              
138             },
139              
140             ( E0 | MIN | MAX | W0 | RATIO ),
141             sub {
142              
143             my $elements = do {
144              
145             if ( $_->e0 < $_->min ) {
146             e0_le_min( $_->ratio, $_->w0, $_->min, $_->max, $_->e0 );
147             }
148              
149             # shrink & grow!
150             elsif ( $_->e0 < $_->max ) {
151              
152             my $low = e0_ge_max( 1 / $_->ratio, $_->w0 / $_->ratio, $_->min, $_->e0, $_->e0, );
153              
154             my $high = e0_le_min( $_->ratio, $_->w0, $_->e0, $_->max, $_->e0 );
155              
156             # low and high share an edge; remove it.
157             my @elements = ( $low->@* );
158             pop @elements;
159             push @elements, $high->@*;
160             \@elements;
161             }
162             else {
163             e0_ge_max( $_->ratio, $_->w0, $_->min, $_->max, $_->e0 );
164             }
165             };
166              
167             return { elements => $elements };
168             },
169              
170             );
171              
172             # need the parens otherwise the => operator turns them into strings
173             ## no critic(ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions)
174             my @ArgsCrossValidate = ( [
175             E0 | MIN | MAX,
176             sub {
177             parameter_constraint->throw( "min < max\n" )
178             unless $_->min < $_->max;
179              
180             parameter_constraint->throw( "w0 > 0 if E[0] <= min\n" )
181             if $_->e0 <= $_->min && $_->w0 < 0;
182              
183             parameter_constraint->throw( "w0 < 0 if E[0] >= max\n" )
184             if $_->e0 >= $_->max && $_->w0 > 0;
185             },
186             ],
187              
188             [
189             MIN | SOFT_MAX,
190             sub {
191             parameter_constraint->throw( "min < soft_max\n" )
192             unless $_->min < $_->soft_max;
193              
194             parameter_constraint->throw( "w0 > 0 if E[0] == min\n" )
195             unless $_->w0 > 0;
196             },
197             ],
198              
199             [
200             SOFT_MIN | MAX,
201             sub {
202             parameter_constraint->throw( "soft_min < max\n" )
203             unless $_->soft_min < $_->max;
204              
205             parameter_constraint->throw( "w0 < 0 if E[0] == max\n" )
206             unless $_->w0 < 0;
207             },
208             ],
209              
210             );
211              
212             around BUILDARGS => buildargs_factory(
213             map => \%ArgMap,
214             build => \%ArgBuild,
215             xvalidate => \@ArgsCrossValidate,
216             );
217              
218              
219              
220              
221              
222              
223              
224              
225              
226              
227              
228              
229              
230              
231              
232              
233              
234              
235              
236              
237              
238              
239              
240              
241              
242              
243              
244              
245              
246              
247              
248              
249              
250              
251              
252              
253              
254              
255              
256              
257              
258              
259              
260              
261              
262              
263              
264              
265              
266              
267              
268              
269              
270              
271              
272              
273              
274              
275              
276              
277              
278              
279              
280              
281              
282              
283              
284              
285              
286              
287              
288              
289              
290              
291              
292              
293              
294              
295              
296              
297              
298              
299              
300              
301              
302              
303              
304              
305              
306             1;
307              
308             __END__
309              
310             =pod
311              
312             =for :stopwords Diab Jerius Smithsonian Astrophysical Observatory extrema extremum spacings
313              
314             =head1 NAME
315              
316             CXC::Number::Sequence::Ratio - Numeric Sequence with Relative Fractional Spacing
317              
318             =head1 VERSION
319              
320             version 0.13
321              
322             =head1 SYNOPSIS
323              
324             use CXC::Number::Sequence::Ratio;
325              
326             $seq = CXC::Number::Sequence::Ratio->new( min = $min, max => $max,
327             w0 => $spacing,
328             ratio => $ratio );
329              
330             $sequence = $seq->elements;
331              
332             =head1 DESCRIPTION
333              
334             B<CXC::Number::Sequence::Ratio> generates an increasing sequence of numbers
335             covering a range, where the ratio of the spacing, I<w[k]>, between
336             consecutive numbers I<E[k]> and I<E[k-1]> is a constant, e.g.,
337              
338             w[k] = r * w[k-1] (1)
339              
340             In general, a number in the sequence is
341              
342             k
343             1 - r
344             E[k] = E[0] + w[0] ------- (2)
345             1 - r
346              
347             Where I<E[0]> is the I<alignment> value, I<w[0]> is the initial spacing,
348             I<E[1]-E[0]>, and I<k> is the I<generating> index (not the output order).
349             The sequence is always output in increasing value, regardless of the
350             order in which it was generated.
351              
352             I<r> must be positive and not equal to I<1> (that would generate a
353             linear sequence and the algorithms used in this module would
354             break). The alignment value, I<E[0]>, need not be one of the range
355             extrema, nor even in the range.
356              
357             If the sequence must cover a specific range, then some
358             caveats apply. If I<< r < 1 >>, Eq. 2 may converge to a value
359             which does not allow covering the specified range:
360              
361             w[0]
362             E[Infinity] = E[0] + ------- (3)
363             1 - r
364              
365             An exception will be thrown if this is the case.
366              
367             If I<< E[0] >= E[max] >> the sequence values are generated from
368             larger to smaller values. C<< w[0] >> must be C<< < 0 >> , and C<r>
369             is the growth factor in the direction of smaller sequence
370             values.
371              
372             It subclasses L<CXC::Number::Sequence>, so see documentation for that
373             class for additional methods.
374              
375             A full description of the available parameters may be found in the
376             description of the constructor L</new>.
377              
378             If an inconsistent set of parameters is passed, C<new> will throw an exception of class
379             C<CXC::Number::Sequence::Failure::parameter::IllegalCombination>.
380              
381             If an unknown parameter is passed, C<new> will throw an exception of class
382             C<CXC::Number::Sequence::Failure::parameter::unknown>.
383              
384             If a parameter value is illegal or a combination of values is illegal
385             (e.g. C<< min > max >>), C<new> will throw an exception of class
386             C<CXC::Number::Sequence::Failure::parameter::constraint>.
387              
388             =head1 CONSTRUCTORS
389              
390             =head2 new
391              
392             $sequence = CXC::Number::Sequence::Ratio->new( %attr );
393              
394             Construct a sequence. The available attributes are those for the parent
395             constructor in L<CXC::Number::Sequence::Base>, as well as the following:
396              
397             Only certain combinations of parameters are allowed; see L</Valid Parameter Combinations>.
398              
399             =head3 Range Parameters
400              
401             Range extrema may be I<hard>, indicating that the sequence must exactly
402             cover the extrema, or I<soft>, indicating that the sequence may cover a
403             larger range. Usually the combination of parameters will uniquely
404             determine whether an extremum is soft or hard, but in some cases soft
405             bounds must be explicitly labeled as soft, requiring use of the
406             C<soft_min> and C<soft_max> parameters.
407              
408             =over
409              
410             =item C<min>
411              
412             =item C<soft_min>
413              
414             The minimum value that the sequence should cover.
415             Use C<soft_min> to disambiguate hard from soft limits as documented above.
416              
417             =item C<max>
418              
419             =item C<soft_max>
420              
421             The maximum value that the sequence should cover.
422             Use C<soft_max> to disambiguate hard from soft limits as documented above.
423              
424             =item C<C<nelem>>
425              
426             The number of elements in the sequence
427              
428             =back
429              
430             =head3 Spacing and Alignment
431              
432             =over
433              
434             =item C<w0>
435              
436             The spacing between I<E[0]> and I<E[1]>. All other spacings are based
437             on this. If C<< E[0] >= max >>, then C<w0> must be negative, otherwise it
438             must be positive. If C<< w[0] >> has the incorrect sign, an exception
439             will be thrown.
440              
441             =item C<e0>
442              
443             C<E[0]>. This is usually implicitly specified by the C<min> or C<max>
444             parameters. Set it explicitly if it is not one of the extrema.
445              
446             =back
447              
448             =head3 Valid Parameter Combinations
449              
450             =over
451              
452             =item C<min>, C<soft_max>, C<w0>, C<ratio>
453              
454             C<E[0] = min>, and the sequence minimally covers the range.
455              
456             =item C<soft_min>, C<max>, C<w0>, C<ratio>
457              
458             C<E[0] = max>, and the sequence minimally covers the range. C<< w0 < 0 >>.
459              
460             =item C<min>, C<nelem>, C<w0>, C<ratio>
461              
462             C<E[0] = min> and the sequence exactly covers the specified range. C<< w0 > 0 >>
463              
464             =item C<max>, C<nelem>, C<w0>, C<ratio>
465              
466             C<E[0] = max>, and the sequence exactly covers the range. C<< w0 < 0 >>.
467              
468             =item C<e0> C<min>, C<max>, C<w0>, C<ratio>
469              
470             C<E[0] = e0>, and the sequence covers the range. C<E[0]>
471             need not be inside the range. C<< w0 < 0 >> if C<< E[0] > max >>.
472              
473             =back
474              
475             =head1 INTERNALS
476              
477             =for Pod::Coverage BUILDARGS
478              
479             # COPYRIGHT
480              
481             =head1 EXAMPLES
482              
483             =over
484              
485             =item 1
486              
487             Range: [2,20]
488             E[0] = 20
489             w[0] = -1
490             r = 1.1
491              
492             Cover the range C<[2, 20]>, with the alignment value at the max of the range. Spacing increases
493             from C<20> to C<2>, starting at C<1>, by a factor of 1.1 per value.
494              
495             use Data::Dump;
496             use aliased 'CXC::Number::Sequence::Ratio';
497             dd Ratio->new(
498             soft_min => 2,
499             max => 20,
500             ratio => 1.1,
501             w0 => -1
502             )->elements;
503              
504             results in
505              
506             [
507             1.4688329389,
508             4.062575399,
509             6.42052309,
510             8.5641119,
511             10.512829,
512             12.28439,
513             13.8949,
514             15.359,
515             16.69,
516             17.9,
517             19,
518             20,
519             ]
520              
521             =back
522              
523             =head1 SUPPORT
524              
525             =head2 Bugs
526              
527             Please report any bugs or feature requests to bug-cxc-number@rt.cpan.org or through the web interface at: L<https://rt.cpan.org/Public/Dist/Display.html?Name=CXC-Number>
528              
529             =head2 Source
530              
531             Source is available at
532              
533             https://gitlab.com/djerius/cxc-number
534              
535             and may be cloned from
536              
537             https://gitlab.com/djerius/cxc-number.git
538              
539             =head1 SEE ALSO
540              
541             Please see those modules/websites for more information related to this module.
542              
543             =over 4
544              
545             =item *
546              
547             L<CXC::Number|CXC::Number>
548              
549             =back
550              
551             =head1 AUTHOR
552              
553             Diab Jerius <djerius@cpan.org>
554              
555             =head1 COPYRIGHT AND LICENSE
556              
557             This software is Copyright (c) 2019 by Smithsonian Astrophysical Observatory.
558              
559             This is free software, licensed under:
560              
561             The GNU General Public License, Version 3, June 2007
562              
563             =cut