File Coverage

blib/lib/CXC/Number/Grid/Tree.pm
Criterion Covered Total %
statement 72 92 78.2
branch 6 10 60.0
condition 2 6 33.3
subroutine 15 19 78.9
pod 8 8 100.0
total 103 135 76.3


line stmt bran cond sub pod time code
1             package CXC::Number::Grid::Tree;
2              
3             # ABSTRACT: A Tree representing a Grid
4              
5 13     13   683275 use v5.28;
  13         54  
6 13     13   76 use strict;
  13         41  
  13         421  
7 13     13   65 use warnings;
  13         23  
  13         913  
8              
9             our $VERSION = '0.13';
10              
11 13     13   952 use parent 'Tree::Range::RB';
  13         684  
  13         83  
12              
13 13     13   200127 use experimental 'signatures';
  13         5202  
  13         107  
14              
15 2795     2795   681451 my sub _key_compare { $_[0] <=> $_[1] }
16              
17             sub _croak {
18 0     0   0 require Carp;
19 0         0 goto \&Carp::croak;
20             }
21              
22              
23              
24              
25              
26              
27              
28              
29              
30              
31 21     21 1 603605 sub new ( $class, $options = {} ) {
  21         53  
  21         52  
  21         42  
32              
33 21         115 my %options = ( cmp => \&_key_compare, $options->%* );
34 21         204 return $class->SUPER::new( \%options );
35             }
36              
37              
38              
39              
40              
41              
42              
43              
44              
45 0     0 1 0 sub to_string ( $self ) {
  0         0  
  0         0  
46 0         0 my $ic = $self->range_iter_closure;
47 0         0 my @string;
48 0         0 while ( my ( $v, $lb, $ub ) = $ic->() ) {
49 0 0       0 $v
    0          
50             = defined $v
51             ? ref $v
52 0         0 ? "[ @{[ join( ', ', $v->@*) ]} ]"
53             : $v
54             : 'undef';
55 0   0     0 $lb //= 'undef';
56 0   0     0 $ub //= 'undef';
57 0         0 push @string, "( $lb, $ub )\t=> $v";
58             }
59              
60 0         0 return join( "\n", @string );
61             }
62              
63              
64              
65              
66              
67              
68              
69              
70              
71              
72              
73              
74 6     6 1 308 sub to_array ( $self ) {
  6         11  
  6         8  
75 6         11 my @arr;
76 6         26 my $ic = $self->range_iter_closure;
77 6         64 $ic->(); # discard lower bound
78 6         211 while ( my ( $v, $lb, $ub ) = $ic->() ) {
79 27         775 push @arr, [ $lb, $ub, $v ];
80             }
81 6         32 pop @arr; # discard upper bound
82 6         68 return \@arr;
83             }
84              
85              
86              
87              
88              
89              
90              
91              
92              
93 1     1 1 114 sub from_array ( $, $ranges ) {
  1         2  
  1         1  
94 1         3 my $tree = __PACKAGE__->new;
95 1         23 $tree->range_set( $_->@* ) for $ranges->@*;
96 1         45 return $tree;
97             }
98              
99              
100              
101              
102              
103              
104              
105              
106              
107 4     4 1 30 sub from_grid ( $, $grid ) {
  4         11  
  4         9  
108 4         20 my $tree = __PACKAGE__->new;
109              
110 4         215 my $edges = $grid->_raw_edges;
111 4         20 my $include = $grid->include;
112              
113 4         114 $tree->range_set( $edges->@[ $_, $_ + 1 ], $include->[$_] ) for 0 .. ( $grid->nbins - 1 );
114 4         1441 return $tree;
115             }
116              
117              
118              
119              
120              
121              
122              
123              
124              
125 15     15 1 5989 sub to_grid ( $self ) {
  15         38  
  15         27  
126              
127 15         34 my @edges;
128             my @include;
129 15         110 my $ic = $self->range_iter_closure;
130              
131              
132             # this is the lower bound
133 15         206 my ( $v, $lower, $upper ) = $ic->();
134 15         862 push @edges, $upper;
135              
136 15         45 while ( ( $v, $lower, $upper ) = $ic->() ) {
137 91 100       2961 if ( defined $upper ) {
138 76         144 push @edges, $upper;
139 76 100 100     301 push @include, ref $v ? $v->[-1] : $v // 0;
140             }
141             }
142 15         1681 require CXC::Number::Grid;
143 15         629 return CXC::Number::Grid->new( edges => \@edges, include => \@include );
144             }
145              
146              
147              
148              
149              
150              
151              
152              
153              
154              
155              
156              
157              
158              
159              
160              
161              
162              
163              
164              
165              
166              
167              
168              
169              
170              
171              
172              
173              
174              
175              
176              
177              
178              
179              
180              
181              
182              
183              
184              
185              
186              
187              
188              
189              
190              
191              
192              
193              
194              
195 8     8 1 744 sub snap_overlaid ( $self, $layer, $snap_to, $snap_dist ) { ## no critic(Subroutines::ProhibitManyArgs)
  8         20  
  8         19  
  8         16  
  8         12  
  8         13  
196              
197 8 100       39 return if $snap_dist == 0;
198              
199             # Tree::Range doesn't represent a range as a node with the ability
200             # to visit a predecessor. It essentially only allows one way
201             # tree traversal, so we need to traverse it forwards to handle
202             # snapping to the right, and backwards to handle snapping to the left.
203 5         1289 $self->_snap_overlaid_edges( $layer, $snap_to, $snap_dist, $_ ) for qw( right left );
204             }
205              
206              
207              
208              
209              
210              
211              
212              
213              
214              
215              
216             sub _snap_overlaid_edges ( $self, $layer, $snap_to, $snap_dist, $scan_direction ) { ## no critic(Subroutines::ProhibitManyArgs)
217              
218             require CXC::Number::Grid::Range;
219              
220             defined( my $scan_reversed = { right => 0, left => 1 }->{$scan_direction} )
221             or _croak( "illegal scan direction: '$scan_direction'" );
222              
223             my sub iter ( $key = undef ) {
224             my $iter = $self->range_iter_closure( $key, $scan_reversed );
225             # first range goes from +-inf to real bound; remove
226             $iter->() unless defined $key;
227             return $iter;
228             }
229              
230             my $iter = iter();
231              
232             my sub next_range {
233             my @r = $iter->();
234             return @r
235             ? CXC::Number::Grid::Range->new( {
236             value => $r[0],
237             lb => $r[1],
238             ub => $r[2],
239             } )
240             : undef;
241             }
242              
243             my $current = next_range();
244             my $next;
245              
246             my %SnapTo = (
247             overlay => {
248             right => sub {
249             # would prefer
250             # $self->range_set( $prev->lb, $current->ub, $current->value );
251             # but there's no way to get $prev from Tree::Range.
252             #
253             # This code depends upon Tree::Range storing [ $lb, $value ]
254             # in each node, so deleting a node extends the previous
255             # range.
256 4     4   17 $self->delete( $current->lb );
257 4         508 $iter = iter( $current->lb );
258             },
259             left => sub {
260 0     0   0 $self->range_set( $next->lb, $current->ub, $next->value );
261 0         0 $iter = iter( $current->ub );
262             },
263             },
264             underlay => {
265             right => sub {
266 1     1   8 $self->range_set( $current->lb, $next->ub, $next->value );
267 1         233 $iter = iter( $current->lb );
268              
269             },
270             left => sub {
271 1     1   6 $self->range_set( $next->lb, $current->ub, $next->value );
272 1         176 $iter = iter( $current->ub );
273             },
274             },
275             );
276              
277             my $snap = $SnapTo{$snap_to}{$scan_direction}
278             or _croak( "unknown layer to snap to: $snap_to" );
279              
280             while ( defined( $next = next_range() ) ) {
281              
282             if ( abs( $current->ub - $current->lb ) <= $snap_dist
283             && $next->layer == $layer
284             && $current->layer < $next->layer )
285             {
286             $snap->();
287             # all of the $snap routines reset the iterator
288             $current = next_range();
289             }
290             else {
291             $current = $next;
292             }
293             }
294             }
295              
296              
297              
298              
299              
300              
301              
302              
303              
304 0     0 1   sub clone ( $self ) {
  0            
  0            
305 0           return __PACKAGE__->from_array( $self->to_array );
306             }
307              
308             1;
309              
310             #
311             # This file is part of CXC-Number
312             #
313             # This software is Copyright (c) 2019 by Smithsonian Astrophysical Observatory.
314             #
315             # This is free software, licensed under:
316             #
317             # The GNU General Public License, Version 3, June 2007
318             #
319              
320             __END__
321              
322             =pod
323              
324             =for :stopwords Diab Jerius Smithsonian Astrophysical Observatory extrema overlaid
325              
326             =head1 NAME
327              
328             CXC::Number::Grid::Tree - A Tree representing a Grid
329              
330             =head1 VERSION
331              
332             version 0.13
333              
334             =head1 DESCRIPTION
335              
336             This is a subclass of L<Tree::Range> which is used to manipulate grids
337              
338             =head1 CONSTRUCTORS
339              
340             =head2 new
341              
342             $tree = CXC::Number::Grid::Tree->new( ?\%options )
343              
344             Construct a new tree, using a default numerical key comparison
345             function. All options recognized by L<Tree::Range::RB> are accepted.
346              
347             =head2 from_array
348              
349             $tree = CXC::Number::Grid::Tree->from_array( \@array );
350              
351             Construct a tree object from an array generated by L</to_array>.
352              
353             =head2 from_grid
354              
355             $tree = CXC::Number::Grid::Tree->from_grid( $grid );
356              
357             Construct a tree object from a L<CXC::Number::Grid> object.
358              
359             =head1 METHODS
360              
361             =head2 to_string
362              
363             $tree->to_string;
364              
365             Return a string representation of the tree.
366              
367             =head2 to_array
368              
369             \@array = $tree->to_array;
370              
371             Return an arrayref with one element per bin. Each element is an
372             arrayref and contains the lower bound, upper bound, and value stored
373             in the tree for the bin bin.
374              
375             =head2 to_grid
376              
377             $grid = $tree->to_grid;
378              
379             Return a L<CXC::Number::Grid> object represented by the tree.
380              
381             =head2 snap_overlaid
382              
383             $tree->snap_overlaid( $layer, $snap_to, $snap_dist ) {
384              
385             Snap overlaid bins' edges.
386              
387             B<Works in place!!>
388              
389             This assumes that the Tree has been
390              
391             =over
392              
393             =item 1
394              
395             loaded with ranges from two grids, one of which overlays the other; and
396              
397             =item 2
398              
399             that the range values are arrayrefs with the first value being the
400             layer id (larger number indicates the top grid); and
401              
402             =item 3
403              
404             that the top grid is contiguous (e.g. no holes through which the
405             lower grid is visible)
406              
407             =back
408              
409             In the general case, the minimum and maximum edges of the top grid
410             will intersect bins in the lower grid. If the remnants of those bins
411             (e.g. the parts not covered by the top grid) are small enough (e.g,
412             smaller than C<$snap_dist> in width), then this routine will either:
413              
414             =over
415              
416             =item *
417              
418             move the outer edge of the top grid to coincide with the remaining edge of the intersected lower bin
419             (C<$snap_to = 'underlay'>)
420              
421             =item *
422              
423             move the remaining edge of the intersected lower bin to coincide with the edge of the top grid.
424             (C<$snap_to = 'overlay'>)
425              
426             =back
427              
428             =head2 clone
429              
430             $clone = $tree->clone;
431              
432             Clone a tree, performing a shallow copy of the values associated with each bin in the tree.
433              
434             =head1 INTERNALS
435              
436             =head2 Methods
437              
438             =head3 _snap_overlaid_edges
439              
440             $self->_snap_overlaid_edges( $layer, $snap_to, $snap_dist, $scan_direction ) {
441              
442             A helper for L</snap_to>. This routine is run twice, with
443             C<$scan_direction> set to C<left> and C<right>, then to handle the
444             extrema of the overlaid grid.
445              
446             =head1 SUPPORT
447              
448             =head2 Bugs
449              
450             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>
451              
452             =head2 Source
453              
454             Source is available at
455              
456             https://gitlab.com/djerius/cxc-number
457              
458             and may be cloned from
459              
460             https://gitlab.com/djerius/cxc-number.git
461              
462             =head1 SEE ALSO
463              
464             Please see those modules/websites for more information related to this module.
465              
466             =over 4
467              
468             =item *
469              
470             L<CXC::Number|CXC::Number>
471              
472             =item *
473              
474             L<CXC::Number::Grid|CXC::Number::Grid>
475              
476             =item *
477              
478             L<CXC::Number::Grid::Range|CXC::Number::Grid::Range>
479              
480             =back
481              
482             =head1 AUTHOR
483              
484             Diab Jerius <djerius@cpan.org>
485              
486             =head1 COPYRIGHT AND LICENSE
487              
488             This software is Copyright (c) 2019 by Smithsonian Astrophysical Observatory.
489              
490             This is free software, licensed under:
491              
492             The GNU General Public License, Version 3, June 2007
493              
494             =cut