File Coverage

blib/lib/Math/PlanePath/UlamWarburton.pm
Criterion Covered Total %
statement 205 278 73.7
branch 83 140 59.2
condition 13 20 65.0
subroutine 24 34 70.5
pod 20 20 100.0
total 345 492 70.1


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Kevin Ryde
2              
3             # This file is part of Math-PlanePath.
4             #
5             # Math-PlanePath is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-PlanePath is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-PlanePath. If not, see .
17              
18              
19             #------------------------------------------------------------------------------
20             # cf
21             # Ulam/Warburton with cells turning off too
22             # A079315 cells OFF -> ON
23             # A079317 cells ON at stage n
24             # A079316 cells ON at stage n, in first quadrant
25             # A151921 net gain ON cells
26              
27              
28             #------------------------------------------------------------------------------
29              
30             package Math::PlanePath::UlamWarburton;
31 1     1   9273 use 5.004;
  1         11  
32 1     1   5 use strict;
  1         2  
  1         24  
33 1     1   4 use Carp 'croak';
  1         3  
  1         81  
34 1     1   8 use List::Util 'sum';
  1         2  
  1         119  
35              
36 1     1   7 use vars '$VERSION', '@ISA';
  1         2  
  1         80  
37             $VERSION = 127;
38 1     1   668 use Math::PlanePath;
  1         3  
  1         63  
39             @ISA = ('Math::PlanePath');
40             *_divrem = \&Math::PlanePath::_divrem;
41             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
42              
43             use Math::PlanePath::Base::Generic
44 1         48 'is_infinite',
45 1     1   7 'round_nearest';
  1         2  
46             use Math::PlanePath::Base::Digits
47 1         64 'round_up_pow',
48             'round_down_pow',
49 1     1   481 'digit_split_lowtohigh';
  1         3  
50              
51 1     1   518 use Math::PlanePath::UlamWarburtonQuarter;
  1         3  
  1         79  
52              
53             # uncomment this to run the ### lines
54             # use Smart::Comments;
55              
56              
57 1         1572 use constant parameter_info_array =>
58             [
59             { name => 'parts',
60             share_key => 'parts_ulamwarburton',
61             display => 'Parts',
62             type => 'enum',
63             default => '4',
64             choices => ['4','2','1','octant','octant_up' ],
65             choices_display => ['4','2','1','Octant','Octant Up' ],
66             description => 'Which parts of the plane to fill.',
67             },
68             Math::PlanePath::Base::Generic::parameter_info_nstart1(),
69 1     1   8 ];
  1         2  
70              
71             # octant_up goes up the Y axis spine, dX=0
72             # all others always have dX!=0
73             sub absdx_minimum {
74 0     0 1 0 my ($self) = @_;
75 0 0       0 return ($self->{'parts'} eq 'octant_up' ? 0 : 1);
76             }
77              
78             # used also to validate $self->{'parts'}
79             my %x_negative = (4 => 1,
80             2 => 1,
81             1 => 0,
82             octant => 0,
83             octant_up => 0,
84             );
85             sub x_negative {
86 1     1 1 3 my ($self) = @_;
87 1         29 return $x_negative{$self->{'parts'}};
88             }
89             sub y_negative {
90 1     1 1 4 my ($self) = @_;
91 1         4 return $self->{'parts'} eq '4';
92             }
93              
94             sub x_negative_at_n {
95 0     0 1 0 my ($self) = @_;
96 0 0       0 return ($x_negative{$self->{'parts'}} ? $self->n_start + 3 : undef);
97             }
98             sub y_negative_at_n {
99 0     0 1 0 my ($self) = @_;
100 0 0       0 return ($self->{'parts'} eq '4' ? $self->n_start + 4 : undef);
101             }
102              
103             sub diffxy_minimum {
104 0     0 1 0 my ($self) = @_;
105 0 0       0 return ($self->{'parts'} eq 'octant' ? 0 : undef);
106             }
107             sub diffxy_maximum {
108 0     0 1 0 my ($self) = @_;
109 0 0       0 return ($self->{'parts'} eq 'octant_up' ? 0 : undef);
110             }
111              
112             {
113             my %dir_maximum_dxdy = (4 => [1,-1], # N=4 South-East
114             2 => [1,-1], # N=44 South-East
115             1 => [2,-1], # N=3 ESE
116             octant => [10,-3], # N=51
117             octant_up => [2,-1], # N=8 ESE
118             );
119             sub dir_maximum_dxdy {
120 0     0 1 0 my ($self) = @_;
121 0         0 return @{$dir_maximum_dxdy{$self->{'parts'}}};
  0         0  
122             }
123             }
124              
125             {
126             my %_UNDOCUMENTED__turn_any_right_at_n
127             = (
128             4 => 20,
129             2 => 35,
130             1 => 2,
131             octant => 4,
132             octant_up => 2,
133             );
134             sub _UNDOCUMENTED__turn_any_right_at_n {
135 0     0   0 my ($self) = @_;
136             return $self->n_start
137 0         0 + $_UNDOCUMENTED__turn_any_right_at_n{$self->{'parts'}};
138             }
139             }
140              
141             sub tree_num_children_list {
142 0     0 1 0 my ($self) = @_;
143 0 0       0 return ($self->{'parts'} eq '4'
144             ? (0, 1, 3, 4)
145             : (0, 1, 2, 3 ));
146             }
147              
148             #------------------------------------------------------------------------------
149             sub new {
150 20     20 1 3592 my $self = shift->SUPER::new(@_);
151 20 100       69 if (! defined $self->{'n_start'}) {
152 9         32 $self->{'n_start'} = $self->default_n_start;
153             }
154 20   100     79 my $parts = ($self->{'parts'} ||= '4');
155 20 50       52 if (! exists $x_negative{$parts}) {
156 0         0 croak "Unrecognised parts option: ", $parts;
157             }
158 20         43 return $self;
159             }
160              
161             sub n_to_xy {
162 370     370 1 8325 my ($self, $n) = @_;
163             ### UlamWarburton n_to_xy(): "$n parts=$self->{'parts'}"
164              
165 370 50       808 if ($n < $self->{'n_start'}) { return; }
  0         0  
166 370 50       779 if (is_infinite($n)) { return ($n,$n); }
  0         0  
167             {
168 370         639 my $int = int($n);
  370         553  
169             ### $int
170             ### $n
171 370 50       711 if ($n != $int) {
172 0         0 my ($x1,$y1) = $self->n_to_xy($int);
173 0         0 my ($x2,$y2) = $self->n_to_xy($int+1);
174 0         0 my $frac = $n - $int; # inherit possible BigFloat
175 0         0 my $dx = $x2-$x1;
176 0         0 my $dy = $y2-$y1;
177 0         0 return ($frac*$dx + $x1, $frac*$dy + $y1);
178             }
179 370         536 $n = $int; # BigFloat int() gives BigInt, use that
180             }
181              
182 370         556 $n = $n - $self->{'n_start'}; # N=0 basis
183 370 100       646 if ($n == 0) { return (0,0); }
  10         30  
184              
185 360         574 my $parts = $self->{'parts'};
186 360 50       702 my ($depthsum, $factor, $nrem) = _n0_to_depthsum_factor_rem($n, $parts)
187             or return $n; # N=nan or +inf
188             ### depthsum: join(',',@$depthsum)
189             ### $factor
190             ### n rem within row: $nrem
191              
192 360 50       742 if ($parts eq '4') {
    0          
    0          
193 360         535 $factor /= 4;
194             } elsif ($parts eq '2') {
195 0         0 $factor /= 2;
196 0         0 $nrem += ($factor-1)/2;
197             } elsif ($parts eq 'octant_up') {
198 0         0 $nrem += $factor;
199             } else {
200 0         0 $nrem += ($factor-1)/2;
201             }
202 360         1048 (my $quad, $nrem) = _divrem ($nrem, $factor);
203              
204             ### factor modulus: $factor
205             ### $quad
206             ### n rem within quad: $nrem
207             ### assert: $quad >= 0
208             ### assert: $quad <= 3
209              
210 360         707 my $dhigh = shift @$depthsum; # highest term
211 360         842 my @ndigits = digit_split_lowtohigh($nrem,3);
212             ### $dhigh
213             ### ndigits low to high: join(',',@ndigits)
214              
215 360         516 my $x = 0;
216 360         488 my $y = 0;
217 360         1022 foreach my $depthterm (reverse @$depthsum) { # depth terms low to high
218 743         1012 my $ndigit = shift @ndigits; # N digits low to high
219             ### $depthterm
220             ### $ndigit
221              
222 743         1050 $x += $depthterm;
223             ### bit to x: "$x,$y"
224              
225 743 100       1171 if ($ndigit) {
226 476 100       835 if ($ndigit == 2) {
227 270         545 ($x,$y) = (-$y,$x); # rotate +90
228             }
229             } else {
230             # $ndigit==0 (or undef when @ndigits shorter than @$depthsum)
231 267         491 ($x,$y) = ($y,-$x); # rotate -90
232             }
233             ### rotate to: "$x,$y"
234             }
235 360         511 $x += $dhigh;
236              
237             ### xy before quad: "$x,$y"
238 360 100       722 if ($quad & 2) {
239 178         258 $x = -$x;
240 178         253 $y = -$y;
241             }
242 360 100       722 if ($quad & 1) {
243 177         293 ($x,$y) = (-$y,$x); # rotate +90
244             }
245              
246             ### final: "$x,$y"
247 360         1076 return $x,$y;
248             }
249             # no Smart::Comments;
250              
251             sub xy_to_n {
252 564     564 1 42557 my ($self, $x, $y) = @_;
253             ### UlamWarburton xy_to_n(): "$x, $y"
254              
255 564         1435 $x = round_nearest ($x);
256 564         1072 $y = round_nearest ($y);
257 564 100 100     1307 if ($x == 0 && $y == 0) {
258 17         38 return $self->{'n_start'};
259             }
260              
261 547         958 my $parts = $self->{'parts'};
262 547 0 0     1150 if ($parts ne '4'
      33        
263             && ($y < 0
264             || ($parts ne '2' && $x < ($parts eq 'octant' ? $y : 0))
265             || ($parts eq 'octant_up' && $x > $y))) {
266 0         0 return undef;
267             }
268              
269 547         714 my $quad;
270 547 100       966 if ($y > $x) {
271             ### quad above leading diagonal ...
272             # /
273             # above /
274             # /
275 262 100       478 if ($y > -$x) {
276             ### quad above opposite diagonal, top quarter ...
277             # top
278             # \ /
279             # \/
280 128         164 $quad = 1;
281 128         236 ($x,$y) = ($y,-$x); # rotate -90
282             } else {
283             ### quad below opposite diagonal, left quarter ...
284             # \
285             # left \
286             # /
287             # /
288 134         223 $quad = 2;
289 134         406 $x = -$x; # rotate -180
290 134         173 $y = -$y;
291             }
292             } else {
293             ### quad below leading diagonal ...
294             # /
295             # / below
296             # /
297 285 100       530 if ($y > -$x) {
298             ### quad above opposite diagonal, right quarter ...
299             # /
300             # / right
301             # \
302             # \
303 144         197 $quad = 0;
304             } else {
305             ### quad below opposite diagonal, bottom quarter ...
306             # /\
307             # / \
308             # bottom
309 141         347 $quad = 3;
310 141         312 ($x,$y) = (-$y,$x); # rotate +90
311             }
312             }
313             ### $quad
314             ### quad rotated xy: "$x,$y"
315             ### assert: ! ($y > $x)
316             ### assert: ! ($y < -$x)
317              
318 547         1417 my ($len, $exp) = round_down_pow ($x + abs($y), 2);
319 547 50       1304 if (is_infinite($exp)) { return ($exp); }
  0         0  
320              
321              
322 547         1081 my $depth =
323             my $ndigits =
324             my $n = ($x * 0 * $y); # inherit bignum 0
325              
326 547         1091 while ($exp-- >= 0) {
327             ### at: "$x,$y n=$n len=$len"
328              
329 1718         2327 my $abs_y = abs($y);
330 1718 100 100     4542 if ($x && $x == $abs_y) {
331 165         406 return undef;
332             }
333              
334             # right quarter diamond
335             ### assert: $x >= 0
336             ### assert: $x >= abs($y)
337             ### assert: $x+abs($y) < 2*$len || $x==abs($y)
338              
339 1553 100       2847 if ($x + $abs_y >= $len) {
340             # one of the three quarter diamonds away from the origin
341 1279         1722 $x -= $len;
342             ### shift to: "$x,$y"
343              
344 1279         1617 $depth += $len;
345 1279 100 100     2873 if ($x || $y) {
346 897         1249 $n *= 3;
347 897         1123 $ndigits++;
348              
349 897 100       1661 if ($y < -$x) {
    100          
350             ### bottom, digit 0 ...
351 321         634 ($x,$y) = (-$y,$x); # rotate +90
352              
353             } elsif ($y > $x) {
354             ### top, digit 2 ...
355 326         586 ($x,$y) = ($y,-$x); # rotate -90
356 326         454 $n += 2;
357             } else {
358             ### right, digit 1 ...
359 250         337 $n += 1;
360             }
361             }
362             }
363              
364 1553         2939 $len /= 2;
365             }
366              
367             ### $n
368             ### $depth
369             ### $ndigits
370             ### npower: 3**$ndigits
371             ### $quad
372             ### quad powered: $quad*3**$ndigits
373              
374 382         679 my $npower = 3**$ndigits;
375 382 50       872 if ($parts eq 'octant_up') {
    50          
376 0         0 $n -= $npower;
377             } elsif ($parts ne '4') {
378 0         0 $n -= ($npower-1)/2;
379             }
380              
381 382         1006 return $n + $quad*$npower + $self->tree_depth_to_n($depth);
382             }
383              
384             # not exact
385             sub rect_to_n_range {
386 50     50 1 4313 my ($self, $x1,$y1, $x2,$y2) = @_;
387             ### UlamWarburton rect_to_n_range(): "$x1,$y1 $x2,$y2"
388              
389 50         150 my ($dlo, $dhi)
390             = _rect_to_diamond_range (round_nearest($x1), round_nearest($y1),
391             round_nearest($x2), round_nearest($y2));
392             ### $dlo
393             ### $dhi
394              
395 50 100       126 if ($dlo) {
396 42         106 ($dlo) = round_down_pow ($dlo,2);
397             }
398 50         118 ($dhi) = round_down_pow ($dhi,2);
399              
400             ### rounded to pow2: "$dlo ".(2*$dhi)
401              
402 50         123 return ($self->tree_depth_to_n($dlo),
403             $self->tree_depth_to_n(2*$dhi) - 1);
404             }
405              
406             # x1 | x2
407             # +--------|-------+ y2 xzero true, yzero false
408             # | | | diamond min is y1
409             # +--------|-------+ y1
410             # |
411             # ----------O-------------
412             #
413             # | x1 x2
414             # | +--------+ y2 xzero false, yzero true
415             # | | | diamond min is x1
416             # -O--------------------
417             # | | |
418             # | +--------+ y1
419             # |
420             #
421             sub _rect_to_diamond_range {
422 50     50   102 my ($x1,$y1, $x2,$y2) = @_;
423              
424 50         106 my $xzero = ($x1 < 0) != ($x2 < 0); # x range covers x=0
425 50         85 my $yzero = ($y1 < 0) != ($y2 < 0); # y range covers y=0
426              
427 50         74 $x1 = abs($x1);
428 50         66 $y1 = abs($y1);
429 50         61 $x2 = abs($x2);
430 50         66 $y2 = abs($y2);
431              
432 50 50       98 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) }
  0         0  
433 50 50       87 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) }
  0         0  
434              
435 50 50       152 return (($yzero ? 0 : $y1) + ($xzero ? 0 : $x1),
    50          
436             $x2+$y2);
437             }
438              
439              
440             #------------------------------------------------------------------------------
441 1     1   8 use constant tree_num_roots => 1;
  1         2  
  1         1292  
442              
443             # ENHANCE-ME: step by the bits, not by X,Y
444             # ENHANCE-ME: tree_n_to_depth() by probe
445             sub tree_n_children {
446 9     9 1 689 my ($self, $n) = @_;
447             ### UlamWarburton tree_n_children(): $n
448              
449 9 50       32 if ($n < $self->{'n_start'}) {
450 0         0 return;
451             }
452 9         23 my ($x,$y) = $self->n_to_xy($n);
453 9         16 my @ret;
454 9         14 my $dx = 1;
455 9         13 my $dy = 0;
456 9         21 foreach (1 .. 4) {
457 36 100       96 if (defined (my $n_child = $self->xy_to_n($x+$dx,$y+$dy))) {
458 28 100       57 if ($n_child > $n) {
459 20         30 push @ret, $n_child;
460             }
461             }
462 36         84 ($dx,$dy) = (-$dy,$dx); # rotate +90
463             }
464 9         38 return sort {$a<=>$b} @ret;
  15         45  
465             }
466             sub tree_n_parent {
467 15     15 1 1083 my ($self, $n) = @_;
468             ### UlamWarburton tree_n_parent(): $n
469              
470 15 100       47 if ($n <= $self->{'n_start'}) {
471 1         3 return undef;
472             }
473 14         31 my ($x,$y) = $self->n_to_xy($n);
474 14         25 my $dx = 1;
475 14         19 my $dy = 0;
476 14         29 foreach (1 .. 4) {
477 37 100       81 if (defined (my $n_parent = $self->xy_to_n($x+$dx,$y+$dy))) {
478 24 100       47 if ($n_parent < $n) {
479 14         32 return $n_parent;
480             }
481             }
482 23         53 ($dx,$dy) = (-$dy,$dx); # rotate +90
483             }
484 0         0 return undef;
485             }
486             # sub tree_n_children {
487             # my ($self, $n) = @_;
488             # my ($power, $exp) = _round_down_pow (3*$n-2, 4);
489             # $exp -= 1;
490             # $power /= 4;
491             #
492             # ### $power
493             # ### $exp
494             # ### pow base: 2 + 4*(4**$exp - 1)/3
495             #
496             # $n -= ($power - 1)/3 * 4 + 2;
497             # ### n less pow base: $n
498             #
499             # my @$depthsum = (2**$exp);
500             # $power = 3**$exp;
501             #
502             # # find the cumulative levelpoints total <= $n, being the start of the
503             # # level containing $n
504             # #
505             # my $factor = 4;
506             # while (--$exp >= 0) {
507             # $power /= 3;
508             # my $sub = 4**$exp * $factor;
509             # ### $sub
510             # # $power*$factor;
511             # my $rem = $n - $sub;
512             #
513             # ### $n
514             # ### $power
515             # ### $factor
516             # ### consider subtract: $sub
517             # ### $rem
518             #
519             # if ($rem >= 0) {
520             # $n = $rem;
521             # push @$depthsum, 2**$exp;
522             # $factor *= 3;
523             # }
524             # }
525             #
526             # $n += $factor;
527             # if (1) {
528             # return ($n,$n+1,$n+2);
529             # } else {
530             # return $n,$n+1,$n+2;
531             # }
532             # }
533              
534             # Converting quarter ...
535             # (N-start)*4+1+start = 4*N-4*start+1+start
536             # = 4*N-3*start+1
537             #
538             sub tree_depth_to_n {
539 620     620 1 10895 my ($self, $depth) = @_;
540             ### UlamWarburton tree_depth_to_n(): $depth
541              
542 620 100       1316 if ($depth == 0) {
543 16         75 return $self->{'n_start'};
544             }
545 604         2056 my $n = $self->Math::PlanePath::UlamWarburtonQuarter::tree_depth_to_n($depth-1);
546 604 50       1254 if (! defined $n) {
547 0         0 return undef;
548             }
549 604         1057 my $parts = $self->{'parts'};
550 604 100       1322 if ($parts eq '2') {
551 16         57 return 2*$n - $self->{'n_start'} + $depth;
552             }
553 588 100       1016 if ($parts eq '1') {
554 61         162 return $n + $depth;
555             }
556 527 50 33     1704 if ($parts eq 'octant' || $parts eq 'octant_up') {
557 0         0 return ($n + 1);
558             }
559             ### assert: $parts eq '4'
560 527         1552 return 4*$n - 3*$self->{'n_start'} + 1;
561             }
562             # sub _NOTWORKING__tree_depth_to_n_range {
563             # my ($self, $depth) = @_;
564             # my ($nstart, $nend) = $self->Math::PlanePath::UlamWarburtonQuarter::tree_depth_to_n_range($self, $depth)
565             # or return;
566             # return (4*$nstart-3 + $self->{'n_start'}-1,
567             # 4*$nend-3 + $self->{'n_start'}-1);
568             # }
569              
570              
571             sub tree_n_to_depth {
572 168     168 1 20100 my ($self, $n) = @_;
573             ### UlamWarburton tree_n_to_depth(): $n
574              
575 168         356 $n = $n - $self->{'n_start'}; # N=0 basis
576 168 50       396 if ($n < 0) {
577 0         0 return undef;
578             }
579 168         236 $n = int($n);
580 168 100       354 if ($n == 0) {
581 16         40 return 0;
582             }
583 152 50       323 my ($depthsum) = _n0_to_depthsum_factor_rem($n, $self->{'parts'})
584             or return $n; # N=nan or +inf
585 152         638 return sum(@$depthsum);
586             }
587              
588              
589             # 1+3+3+9=16
590             #
591             # 0 +1
592             # 1 +4 <- 0
593             # 5 +4 <- 1
594             # 9 +12
595             # 21 +4 <- 5 + 4+12 = 21 = 5 + 4*(1+3)
596             # 25 +12
597             # 37 +12
598             # 49 +36
599             # 85 +4 <- 21 + 4+12+12+36 = 21 + 4*(1+3+3+9)
600             # 89 +12 <- 8 +64
601             # 101 +12
602             # 113 +36
603             # 149
604             # 161
605             # 197
606             # 233
607             # 341
608             # 345 <- 16 +256
609             # 357
610             # 369
611              
612             # 1+3 = 4 power 2
613             # 1+3+3+9 = 16 power 3
614             # 1+3+3+9+3+9+9+27 = 64 power 4
615             #
616             # 4*(1+4+...+4^(l-1)) = 4*(4^l-1)/3
617             # l=1 total=4*(4-1)/3 = 4
618             # l=2 total=4*(16-1)/3=4*5 = 20
619             # l=3 total=4*(64-1)/3=4*63/3 = 4*21 = 84
620             #
621             # n = 2 + 4*(4^l-1)/3
622             # (n-2) = 4*(4^l-1)/3
623             # 3*(n-2) = 4*(4^l-1)
624             # 3n-6 = 4^(l+1)-4
625             # 3n-2 = 4^(l+1)
626             #
627             # 3^0+3^1+3^1+3^2 = 1+3+3+9=16
628             # x+3x+3x+9x = 16x = 256
629             # 4 quads is 4*16=64
630             #
631             # 1+1+3 = 5
632             # 1+1+3 +1+1+3 +3+3+9 = 25
633              
634             # 1+4 = 5
635             # 1+4+4+12 = 21 = 1 + 4*(1+1+3)
636             # 2 +1
637             # 3 +3
638             # 6 +1
639             # 7 +1
640             # 10 +3
641             # 13
642              
643              
644             # parts=1
645             # 1+4+...+4^(l-1) + 2^l
646             # = (4^l-1)/3 + 2^l
647             # = (4^l-1 + 3*2^l)/3
648             # = (2^l*(2^l + 3) - 1)/3
649             # l=1 total= 3
650             # l=2 total= 9
651             # l=3 total= 29
652             # l=4 total= 101
653             #
654             # N = (4^l-1)/3 + 2^l
655             # 3*(N-2^l)+1 = 4^l
656             # 12*(N-2^l)+1 = 4 * 4^l
657             #
658             # parts=2
659             # N = 2*(4^l-1)/3 + 2^l
660             # 3/2*(N-2^l)+1 = 4^l
661             # 6*(N-2^l)+1 = 4 * 4^l
662             #
663             # parts=4
664             # N = (4^l-1)/3
665             # 3*N+1 = 4 * 4^l
666              
667             # use Smart::Comments;
668              
669             # Return ($aref, $factor, $remaining_n).
670             # sum(@$aref) = depth starting depth=1
671             #
672             sub _n0_to_depthsum_factor_rem {
673 512     512   915 my ($n, $parts) = @_;
674             ### _n0_to_depthsum_factor_rem(): "$n parts=$parts"
675              
676 512 50       1096 my $factor = ($parts eq '4' ? 4 : $parts eq '2' ? 2 : 1);
    100          
677 512 50       887 if ($n == 0) {
678 0         0 return ([], $factor, 0);
679             }
680              
681 512         911 my $n3 = 3*$n + 1;
682 512         718 my $ndepth = 0;
683 512         671 my $power = $n3;
684 512         724 my $exp;
685 512 100       960 if ($parts eq '4') {
    50          
    50          
686 428         704 $power /= 4;
687             } elsif ($parts eq '2') {
688 0         0 $power /= 2;
689 0         0 $ndepth = -1;
690             } elsif ($parts =~ /octant/) {
691 0         0 $power *= 2;
692 0         0 $ndepth = 2;
693             }
694 512         1175 ($power, $exp) = round_down_pow ($power, 4);
695             ### $n3
696             ### $power
697             ### $exp
698 512 50       1221 if (is_infinite($exp)) {
699 0         0 return;
700             }
701              
702             # ### pow base: ($power - 1)/3 * $factor + 1 + ($parts ne '4' && $exp)
703             # $n -= ($power - 1)/3 * $factor + 1;
704             # if ($parts ne '4') { $n -= $exp; }
705             # ### n less pow base: $n
706              
707 512         976 my $twopow = 2**$exp;
708 512         769 my @depthsum;
709              
710 512         1003 for (;
711             $exp-- >= 0;
712             $power /= 4, $twopow /= 2) {
713             ### at: "power=$power twopow=$twopow factor=$factor n3=$n3 ndepth=$ndepth depthsum=".join(',',@depthsum)
714              
715 1821         2619 my $nmore = $power * $factor;
716 1821 100       2967 if ($parts ne '4') { $nmore += 3*$twopow; }
  268         395  
717 1821 50       2996 if ($parts =~ /octant/) {
718             ### assert: $nmore % 2 == 0
719 0         0 $nmore = $nmore/2;
720             }
721              
722 1821         2496 my $ncmp = $ndepth + $nmore;
723             ### $nmore
724             ### $ncmp
725              
726 1821 100       3388 if ($n3 >= $ncmp) {
727             ### go to ncmp, remainder: $n3-$ncmp
728 1371         1789 $factor *= 3;
729 1371         1759 $ndepth = $ncmp;
730 1371         3342 push @depthsum, $twopow;
731             }
732             }
733              
734 512 50       1318 if ($parts eq '2') {
735 0         0 $n3 += 1;
736             }
737              
738             # ### assert: ($n3 - $ndepth)%3 == 0
739 512         745 $n = ($n3 - $ndepth) / 3;
740 512         740 $factor /= 3;
741              
742             ### $ndepth
743             ### @depthsum
744             ### remaining n: $n
745             ### assert: $n >= 0
746             ### assert: $n < $factor + ($parts ne '4')
747              
748 512         1583 return \@depthsum, $factor, $n;
749             }
750              
751             sub tree_n_to_subheight {
752 0     0 1 0 my ($self, $n) = @_;
753             ### tree_n_to_subheight(): $n
754              
755 0         0 $n = int($n - $self->{'n_start'}); # N=0 basis
756 0 0       0 if ($n < 0) {
757 0         0 return undef;
758             }
759 0 0       0 my ($depthsum, $factor, $nrem) = _n0_to_depthsum_factor_rem($n, $self->{'parts'})
760             or return $n; # N=nan or +inf
761             ### $depthsum
762             ### $factor
763             ### $nrem
764              
765 0         0 my $parts = $self->{'parts'};
766 0 0       0 if ($parts eq '4') {
    0          
    0          
767 0         0 $factor /= 4;
768             } elsif ($parts eq '2') {
769 0         0 $factor /= 2;
770 0         0 $nrem += ($factor-1)/2;
771             } elsif ($parts eq 'octant_up') {
772             } else {
773 0         0 $nrem += ($factor-1)/2;
774             }
775 0         0 (my $quad, $nrem) = _divrem ($nrem, $factor);
776              
777 0         0 my $sub = pop @$depthsum;
778 0         0 while (_divrem_mutate($nrem,3) == 1) { # low "1" ternary digits of Nrem
779 0         0 $sub += pop @$depthsum;
780             }
781 0 0       0 if (@$depthsum) {
782 0         0 return $depthsum->[-1] - 1 - $sub;
783             } else {
784 0         0 return undef; # N all 1-digits, on central infinite spine
785             }
786             }
787              
788             #------------------------------------------------------------------------------
789             # levels
790              
791             sub level_to_n_range {
792 29     29 1 3925 my ($self, $level) = @_;
793 29         128 return ($self->{'n_start'},
794             $self->tree_depth_to_n_end(2**($level+1)-1));
795             }
796             sub n_to_level {
797 0     0 1   my ($self, $n) = @_;
798 0           my $depth = $self->tree_n_to_depth($n);
799 0 0         if (! defined $depth) { return undef; }
  0            
800 0           my ($pow, $exp) = round_down_pow ($depth, 2);
801 0           return $exp;
802             }
803              
804             # parts=4
805             # Ndepth(2^a) = 2 + 4*(4^a-1)/3
806             # Nend(2^a-1) = 1 + 4*(4^a-1)/3 = (4^(a+1)-1)/3
807             # parts=2
808             #
809             # {
810             # my %factor = (4 => 16,
811             # 2 => 8,
812             # 1 => 4,
813             # octant => 2,
814             # octant_up => 2,
815             # );
816             # my %constant = (4 => -4,
817             # 2 => -5,
818             # 1 => -4,
819             # octant => 0,
820             # octant_up => 0,
821             # );
822             # my %spine = (4 => 0,
823             # 2 => 2,
824             # 1 => 2,
825             # octant => 1,
826             # octant_up => 1,
827             # );
828             # sub level_to_n_range {
829             # my ($self, $level) = @_;
830             # my $parts = $self->{'parts'};
831             # return ($self->{'n_start'},
832             # $self->{'n_start'}
833             # + (4**$level * $factor{$parts} + $constant{$parts}) / 3
834             # + 2**$level * $spine{$parts});
835             # }
836             # }
837              
838             #------------------------------------------------------------------------------
839             1;
840             __END__