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, 2019, 2020 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   9526 use 5.004;
  1         10  
32 1     1   6 use strict;
  1         1  
  1         24  
33 1     1   5 use Carp 'croak';
  1         2  
  1         63  
34 1     1   6 use List::Util 'sum';
  1         2  
  1         113  
35              
36 1     1   21 use vars '$VERSION', '@ISA';
  1         9  
  1         57  
37             $VERSION = 129;
38 1     1   698 use Math::PlanePath;
  1         3  
  1         58  
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         47 'is_infinite',
45 1     1   6 'round_nearest';
  1         2  
46             use Math::PlanePath::Base::Digits
47 1         62 'round_up_pow',
48             'round_down_pow',
49 1     1   537 'digit_split_lowtohigh';
  1         2  
50              
51 1     1   565 use Math::PlanePath::UlamWarburtonQuarter;
  1         2  
  1         62  
52              
53             # uncomment this to run the ### lines
54             # use Smart::Comments;
55              
56              
57 1         1548 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   7 ];
  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         6 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 3330 my $self = shift->SUPER::new(@_);
151 20 100       62 if (! defined $self->{'n_start'}) {
152 9         32 $self->{'n_start'} = $self->default_n_start;
153             }
154 20   100     69 my $parts = ($self->{'parts'} ||= '4');
155 20 50       49 if (! exists $x_negative{$parts}) {
156 0         0 croak "Unrecognised parts option: ", $parts;
157             }
158 20         46 return $self;
159             }
160              
161             sub n_to_xy {
162 370     370 1 8109 my ($self, $n) = @_;
163             ### UlamWarburton n_to_xy(): "$n parts=$self->{'parts'}"
164              
165 370 50       798 if ($n < $self->{'n_start'}) { return; }
  0         0  
166 370 50       821 if (is_infinite($n)) { return ($n,$n); }
  0         0  
167             {
168 370         672 my $int = int($n);
  370         512  
169             ### $int
170             ### $n
171 370 50       718 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         569 $n = $int; # BigFloat int() gives BigInt, use that
180             }
181              
182 370         668 $n = $n - $self->{'n_start'}; # N=0 basis
183 370 100       738 if ($n == 0) { return (0,0); }
  7         21  
184              
185 363         586 my $parts = $self->{'parts'};
186 363 50       665 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 363 50       758 if ($parts eq '4') {
    0          
    0          
193 363         572 $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 363         904 (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 363         671 my $dhigh = shift @$depthsum; # highest term
211 363         800 my @ndigits = digit_split_lowtohigh($nrem,3);
212             ### $dhigh
213             ### ndigits low to high: join(',',@ndigits)
214              
215 363         528 my $x = 0;
216 363         498 my $y = 0;
217 363         683 foreach my $depthterm (reverse @$depthsum) { # depth terms low to high
218 723         1043 my $ndigit = shift @ndigits; # N digits low to high
219             ### $depthterm
220             ### $ndigit
221              
222 723         1034 $x += $depthterm;
223             ### bit to x: "$x,$y"
224              
225 723 100       1183 if ($ndigit) {
226 456 100       798 if ($ndigit == 2) {
227 272         552 ($x,$y) = (-$y,$x); # rotate +90
228             }
229             } else {
230             # $ndigit==0 (or undef when @ndigits shorter than @$depthsum)
231 267         525 ($x,$y) = ($y,-$x); # rotate -90
232             }
233             ### rotate to: "$x,$y"
234             }
235 363         586 $x += $dhigh;
236              
237             ### xy before quad: "$x,$y"
238 363 100       725 if ($quad & 2) {
239 179         266 $x = -$x;
240 179         274 $y = -$y;
241             }
242 363 100       676 if ($quad & 1) {
243 183         319 ($x,$y) = (-$y,$x); # rotate +90
244             }
245              
246             ### final: "$x,$y"
247 363         976 return $x,$y;
248             }
249             # no Smart::Comments;
250              
251             sub xy_to_n {
252 564     564 1 42349 my ($self, $x, $y) = @_;
253             ### UlamWarburton xy_to_n(): "$x, $y"
254              
255 564         1489 $x = round_nearest ($x);
256 564         1046 $y = round_nearest ($y);
257 564 100 100     1371 if ($x == 0 && $y == 0) {
258 14         32 return $self->{'n_start'};
259             }
260              
261 550         938 my $parts = $self->{'parts'};
262 550 0 0     1049 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 550         741 my $quad;
270 550 100       1009 if ($y > $x) {
271             ### quad above leading diagonal ...
272             # /
273             # above /
274             # /
275 257 100       462 if ($y > -$x) {
276             ### quad above opposite diagonal, top quarter ...
277             # top
278             # \ /
279             # \/
280 128         185 $quad = 1;
281 128         240 ($x,$y) = ($y,-$x); # rotate -90
282             } else {
283             ### quad below opposite diagonal, left quarter ...
284             # \
285             # left \
286             # /
287             # /
288 129         248 $quad = 2;
289 129         191 $x = -$x; # rotate -180
290 129         177 $y = -$y;
291             }
292             } else {
293             ### quad below leading diagonal ...
294             # /
295             # / below
296             # /
297 293 100       512 if ($y > -$x) {
298             ### quad above opposite diagonal, right quarter ...
299             # /
300             # / right
301             # \
302             # \
303 146         188 $quad = 0;
304             } else {
305             ### quad below opposite diagonal, bottom quarter ...
306             # /\
307             # / \
308             # bottom
309 147         193 $quad = 3;
310 147         325 ($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 550         1368 my ($len, $exp) = round_down_pow ($x + abs($y), 2);
319 550 50       1201 if (is_infinite($exp)) { return ($exp); }
  0         0  
320              
321              
322 550         1108 my $depth =
323             my $ndigits =
324             my $n = ($x * 0 * $y); # inherit bignum 0
325              
326 550         1094 while ($exp-- >= 0) {
327             ### at: "$x,$y n=$n len=$len"
328              
329 1705         2463 my $abs_y = abs($y);
330 1705 100 100     4321 if ($x && $x == $abs_y) {
331 165         430 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 1540 100       3053 if ($x + $abs_y >= $len) {
340             # one of the three quarter diamonds away from the origin
341 1262         1716 $x -= $len;
342             ### shift to: "$x,$y"
343              
344 1262         1558 $depth += $len;
345 1262 100 100     2827 if ($x || $y) {
346 877         1184 $n *= 3;
347 877         1151 $ndigits++;
348              
349 877 100       1674 if ($y < -$x) {
    100          
350             ### bottom, digit 0 ...
351 321         615 ($x,$y) = (-$y,$x); # rotate +90
352              
353             } elsif ($y > $x) {
354             ### top, digit 2 ...
355 328         565 ($x,$y) = ($y,-$x); # rotate -90
356 328         506 $n += 2;
357             } else {
358             ### right, digit 1 ...
359 228         300 $n += 1;
360             }
361             }
362             }
363              
364 1540         3030 $len /= 2;
365             }
366              
367             ### $n
368             ### $depth
369             ### $ndigits
370             ### npower: 3**$ndigits
371             ### $quad
372             ### quad powered: $quad*3**$ndigits
373              
374 385         577 my $npower = 3**$ndigits;
375 385 50       882 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 385         886 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 4149 my ($self, $x1,$y1, $x2,$y2) = @_;
387             ### UlamWarburton rect_to_n_range(): "$x1,$y1 $x2,$y2"
388              
389 50         133 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       109 if ($dlo) {
396 45         113 ($dlo) = round_down_pow ($dlo,2);
397             }
398 50         156 ($dhi) = round_down_pow ($dhi,2);
399              
400             ### rounded to pow2: "$dlo ".(2*$dhi)
401              
402 50         121 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         89 my $xzero = ($x1 < 0) != ($x2 < 0); # x range covers x=0
425 50         79 my $yzero = ($y1 < 0) != ($y2 < 0); # y range covers y=0
426              
427 50         75 $x1 = abs($x1);
428 50         68 $y1 = abs($y1);
429 50         66 $x2 = abs($x2);
430 50         62 $y2 = abs($y2);
431              
432 50 50       99 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) }
  0         0  
433 50 50       89 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) }
  0         0  
434              
435 50 50       142 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         1320  
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 810 my ($self, $n) = @_;
447             ### UlamWarburton tree_n_children(): $n
448              
449 9 50       29 if ($n < $self->{'n_start'}) {
450 0         0 return;
451             }
452 9         23 my ($x,$y) = $self->n_to_xy($n);
453 9         12 my @ret;
454 9         16 my $dx = 1;
455 9         12 my $dy = 0;
456 9         22 foreach (1 .. 4) {
457 36 100       81 if (defined (my $n_child = $self->xy_to_n($x+$dx,$y+$dy))) {
458 28 100       53 if ($n_child > $n) {
459 20         35 push @ret, $n_child;
460             }
461             }
462 36         108 ($dx,$dy) = (-$dy,$dx); # rotate +90
463             }
464 9         39 return sort {$a<=>$b} @ret;
  15         41  
465             }
466             sub tree_n_parent {
467 15     15 1 1173 my ($self, $n) = @_;
468             ### UlamWarburton tree_n_parent(): $n
469              
470 15 100       57 if ($n <= $self->{'n_start'}) {
471 1         5 return undef;
472             }
473 14         30 my ($x,$y) = $self->n_to_xy($n);
474 14         23 my $dx = 1;
475 14         17 my $dy = 0;
476 14         28 foreach (1 .. 4) {
477 37 100       76 if (defined (my $n_parent = $self->xy_to_n($x+$dx,$y+$dy))) {
478 24 100       44 if ($n_parent < $n) {
479 14         33 return $n_parent;
480             }
481             }
482 23         54 ($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 623     623 1 7827 my ($self, $depth) = @_;
540             ### UlamWarburton tree_depth_to_n(): $depth
541              
542 623 100       1300 if ($depth == 0) {
543 13         39 return $self->{'n_start'};
544             }
545 610         1963 my $n = $self->Math::PlanePath::UlamWarburtonQuarter::tree_depth_to_n($depth-1);
546 610 50       1310 if (! defined $n) {
547 0         0 return undef;
548             }
549 610         1020 my $parts = $self->{'parts'};
550 610 100       1212 if ($parts eq '2') {
551 16         55 return 2*$n - $self->{'n_start'} + $depth;
552             }
553 594 100       1073 if ($parts eq '1') {
554 61         144 return $n + $depth;
555             }
556 533 50 33     1575 if ($parts eq 'octant' || $parts eq 'octant_up') {
557 0         0 return ($n + 1);
558             }
559             ### assert: $parts eq '4'
560 533         1484 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 13052 my ($self, $n) = @_;
573             ### UlamWarburton tree_n_to_depth(): $n
574              
575 168         346 $n = $n - $self->{'n_start'}; # N=0 basis
576 168 50       372 if ($n < 0) {
577 0         0 return undef;
578             }
579 168         238 $n = int($n);
580 168 100       312 if ($n == 0) {
581 16         47 return 0;
582             }
583 152 50       324 my ($depthsum) = _n0_to_depthsum_factor_rem($n, $self->{'parts'})
584             or return $n; # N=nan or +inf
585 152         579 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 515     515   914 my ($n, $parts) = @_;
674             ### _n0_to_depthsum_factor_rem(): "$n parts=$parts"
675              
676 515 50       1074 my $factor = ($parts eq '4' ? 4 : $parts eq '2' ? 2 : 1);
    100          
677 515 50       943 if ($n == 0) {
678 0         0 return ([], $factor, 0);
679             }
680              
681 515         800 my $n3 = 3*$n + 1;
682 515         721 my $ndepth = 0;
683 515         671 my $power = $n3;
684 515         656 my $exp;
685 515 100       950 if ($parts eq '4') {
    50          
    50          
686 431         667 $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 515         1130 ($power, $exp) = round_down_pow ($power, 4);
695             ### $n3
696             ### $power
697             ### $exp
698 515 50       1155 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 515         926 my $twopow = 2**$exp;
708 515         768 my @depthsum;
709              
710 515         938 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 1808         2627 my $nmore = $power * $factor;
716 1808 100       3217 if ($parts ne '4') { $nmore += 3*$twopow; }
  268         375  
717 1808 50       2973 if ($parts =~ /octant/) {
718             ### assert: $nmore % 2 == 0
719 0         0 $nmore = $nmore/2;
720             }
721              
722 1808         2390 my $ncmp = $ndepth + $nmore;
723             ### $nmore
724             ### $ncmp
725              
726 1808 100       3385 if ($n3 >= $ncmp) {
727             ### go to ncmp, remainder: $n3-$ncmp
728 1354         1758 $factor *= 3;
729 1354         1803 $ndepth = $ncmp;
730 1354         3348 push @depthsum, $twopow;
731             }
732             }
733              
734 515 50       953 if ($parts eq '2') {
735 0         0 $n3 += 1;
736             }
737              
738             # ### assert: ($n3 - $ndepth)%3 == 0
739 515         821 $n = ($n3 - $ndepth) / 3;
740 515         680 $factor /= 3;
741              
742             ### $ndepth
743             ### @depthsum
744             ### remaining n: $n
745             ### assert: $n >= 0
746             ### assert: $n < $factor + ($parts ne '4')
747              
748 515         1762 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 2042 my ($self, $level) = @_;
793 29         121 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__