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   7514 use 5.004;
  1         7  
32 1     1   5 use strict;
  1         1  
  1         18  
33 1     1   4 use Carp 'croak';
  1         2  
  1         54  
34 1     1   6 use List::Util 'sum';
  1         1  
  1         101  
35              
36 1     1   6 use vars '$VERSION', '@ISA';
  1         9  
  1         53  
37             $VERSION = 128;
38 1     1   533 use Math::PlanePath;
  1         2  
  1         49  
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         38 'is_infinite',
45 1     1   5 'round_nearest';
  1         2  
46             use Math::PlanePath::Base::Digits
47 1         50 'round_up_pow',
48             'round_down_pow',
49 1     1   359 'digit_split_lowtohigh';
  1         2  
50              
51 1     1   426 use Math::PlanePath::UlamWarburtonQuarter;
  1         2  
  1         51  
52              
53             # uncomment this to run the ### lines
54             # use Smart::Comments;
55              
56              
57 1         1279 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   6 ];
  1         1  
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         4 return $x_negative{$self->{'parts'}};
88             }
89             sub y_negative {
90 1     1 1 3 my ($self) = @_;
91 1         3 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 2464 my $self = shift->SUPER::new(@_);
151 20 100       57 if (! defined $self->{'n_start'}) {
152 9         29 $self->{'n_start'} = $self->default_n_start;
153             }
154 20   100     75 my $parts = ($self->{'parts'} ||= '4');
155 20 50       41 if (! exists $x_negative{$parts}) {
156 0         0 croak "Unrecognised parts option: ", $parts;
157             }
158 20         36 return $self;
159             }
160              
161             sub n_to_xy {
162 370     370 1 6775 my ($self, $n) = @_;
163             ### UlamWarburton n_to_xy(): "$n parts=$self->{'parts'}"
164              
165 370 50       649 if ($n < $self->{'n_start'}) { return; }
  0         0  
166 370 50       624 if (is_infinite($n)) { return ($n,$n); }
  0         0  
167             {
168 370         555 my $int = int($n);
  370         425  
169             ### $int
170             ### $n
171 370 50       552 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         450 $n = $int; # BigFloat int() gives BigInt, use that
180             }
181              
182 370         485 $n = $n - $self->{'n_start'}; # N=0 basis
183 370 100       530 if ($n == 0) { return (0,0); }
  7         30  
184              
185 363         531 my $parts = $self->{'parts'};
186 363 50       567 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       604 if ($parts eq '4') {
    0          
    0          
193 363         490 $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         775 (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         566 my $dhigh = shift @$depthsum; # highest term
211 363         700 my @ndigits = digit_split_lowtohigh($nrem,3);
212             ### $dhigh
213             ### ndigits low to high: join(',',@ndigits)
214              
215 363         427 my $x = 0;
216 363         419 my $y = 0;
217 363         532 foreach my $depthterm (reverse @$depthsum) { # depth terms low to high
218 729         1188 my $ndigit = shift @ndigits; # N digits low to high
219             ### $depthterm
220             ### $ndigit
221              
222 729         851 $x += $depthterm;
223             ### bit to x: "$x,$y"
224              
225 729 100       968 if ($ndigit) {
226 464 100       679 if ($ndigit == 2) {
227 268         445 ($x,$y) = (-$y,$x); # rotate +90
228             }
229             } else {
230             # $ndigit==0 (or undef when @ndigits shorter than @$depthsum)
231 265         404 ($x,$y) = ($y,-$x); # rotate -90
232             }
233             ### rotate to: "$x,$y"
234             }
235 363         452 $x += $dhigh;
236              
237             ### xy before quad: "$x,$y"
238 363 100       639 if ($quad & 2) {
239 176         203 $x = -$x;
240 176         194 $y = -$y;
241             }
242 363 100       532 if ($quad & 1) {
243 177         282 ($x,$y) = (-$y,$x); # rotate +90
244             }
245              
246             ### final: "$x,$y"
247 363         859 return $x,$y;
248             }
249             # no Smart::Comments;
250              
251             sub xy_to_n {
252 564     564 1 33640 my ($self, $x, $y) = @_;
253             ### UlamWarburton xy_to_n(): "$x, $y"
254              
255 564         1029 $x = round_nearest ($x);
256 564         940 $y = round_nearest ($y);
257 564 100 100     1126 if ($x == 0 && $y == 0) {
258 14         26 return $self->{'n_start'};
259             }
260              
261 550         791 my $parts = $self->{'parts'};
262 550 0 0     854 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         583 my $quad;
270 550 100       833 if ($y > $x) {
271             ### quad above leading diagonal ...
272             # /
273             # above /
274             # /
275 260 100       365 if ($y > -$x) {
276             ### quad above opposite diagonal, top quarter ...
277             # top
278             # \ /
279             # \/
280 128         138 $quad = 1;
281 128         219 ($x,$y) = ($y,-$x); # rotate -90
282             } else {
283             ### quad below opposite diagonal, left quarter ...
284             # \
285             # left \
286             # /
287             # /
288 132         246 $quad = 2;
289 132         147 $x = -$x; # rotate -180
290 132         183 $y = -$y;
291             }
292             } else {
293             ### quad below leading diagonal ...
294             # /
295             # / below
296             # /
297 290 100       398 if ($y > -$x) {
298             ### quad above opposite diagonal, right quarter ...
299             # /
300             # / right
301             # \
302             # \
303 149         180 $quad = 0;
304             } else {
305             ### quad below opposite diagonal, bottom quarter ...
306             # /\
307             # / \
308             # bottom
309 141         155 $quad = 3;
310 141         245 ($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         1113 my ($len, $exp) = round_down_pow ($x + abs($y), 2);
319 550 50       1035 if (is_infinite($exp)) { return ($exp); }
  0         0  
320              
321              
322 550         926 my $depth =
323             my $ndigits =
324             my $n = ($x * 0 * $y); # inherit bignum 0
325              
326 550         900 while ($exp-- >= 0) {
327             ### at: "$x,$y n=$n len=$len"
328              
329 1718         1819 my $abs_y = abs($y);
330 1718 100 100     3658 if ($x && $x == $abs_y) {
331 165         328 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       2253 if ($x + $abs_y >= $len) {
340             # one of the three quarter diamonds away from the origin
341 1268         1436 $x -= $len;
342             ### shift to: "$x,$y"
343              
344 1268         1366 $depth += $len;
345 1268 100 100     2495 if ($x || $y) {
346 883         940 $n *= 3;
347 883         893 $ndigits++;
348              
349 883 100       1358 if ($y < -$x) {
    100          
350             ### bottom, digit 0 ...
351 319         496 ($x,$y) = (-$y,$x); # rotate +90
352              
353             } elsif ($y > $x) {
354             ### top, digit 2 ...
355 324         462 ($x,$y) = ($y,-$x); # rotate -90
356 324         367 $n += 2;
357             } else {
358             ### right, digit 1 ...
359 240         268 $n += 1;
360             }
361             }
362             }
363              
364 1553         2299 $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         507 my $npower = 3**$ndigits;
375 385 50       693 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         759 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 3456 my ($self, $x1,$y1, $x2,$y2) = @_;
387             ### UlamWarburton rect_to_n_range(): "$x1,$y1 $x2,$y2"
388              
389 50         107 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       106 if ($dlo) {
396 45         88 ($dlo) = round_down_pow ($dlo,2);
397             }
398 50         103 ($dhi) = round_down_pow ($dhi,2);
399              
400             ### rounded to pow2: "$dlo ".(2*$dhi)
401              
402 50         98 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   83 my ($x1,$y1, $x2,$y2) = @_;
423              
424 50         71 my $xzero = ($x1 < 0) != ($x2 < 0); # x range covers x=0
425 50         63 my $yzero = ($y1 < 0) != ($y2 < 0); # y range covers y=0
426              
427 50         59 $x1 = abs($x1);
428 50         62 $y1 = abs($y1);
429 50         49 $x2 = abs($x2);
430 50         50 $y2 = abs($y2);
431              
432 50 50       76 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) }
  0         0  
433 50 50       69 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) }
  0         0  
434              
435 50 50       112 return (($yzero ? 0 : $y1) + ($xzero ? 0 : $x1),
    50          
436             $x2+$y2);
437             }
438              
439              
440             #------------------------------------------------------------------------------
441 1     1   7 use constant tree_num_roots => 1;
  1         2  
  1         1010  
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 580 my ($self, $n) = @_;
447             ### UlamWarburton tree_n_children(): $n
448              
449 9 50       24 if ($n < $self->{'n_start'}) {
450 0         0 return;
451             }
452 9         19 my ($x,$y) = $self->n_to_xy($n);
453 9         17 my @ret;
454 9         12 my $dx = 1;
455 9         10 my $dy = 0;
456 9         15 foreach (1 .. 4) {
457 36 100       74 if (defined (my $n_child = $self->xy_to_n($x+$dx,$y+$dy))) {
458 28 100       42 if ($n_child > $n) {
459 20         29 push @ret, $n_child;
460             }
461             }
462 36         64 ($dx,$dy) = (-$dy,$dx); # rotate +90
463             }
464 9         28 return sort {$a<=>$b} @ret;
  15         35  
465             }
466             sub tree_n_parent {
467 15     15 1 950 my ($self, $n) = @_;
468             ### UlamWarburton tree_n_parent(): $n
469              
470 15 100       37 if ($n <= $self->{'n_start'}) {
471 1         13 return undef;
472             }
473 14         27 my ($x,$y) = $self->n_to_xy($n);
474 14         18 my $dx = 1;
475 14         17 my $dy = 0;
476 14         25 foreach (1 .. 4) {
477 37 100       67 if (defined (my $n_parent = $self->xy_to_n($x+$dx,$y+$dy))) {
478 24 100       32 if ($n_parent < $n) {
479 14         27 return $n_parent;
480             }
481             }
482 23         43 ($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 5847 my ($self, $depth) = @_;
540             ### UlamWarburton tree_depth_to_n(): $depth
541              
542 623 100       956 if ($depth == 0) {
543 13         28 return $self->{'n_start'};
544             }
545 610         1513 my $n = $self->Math::PlanePath::UlamWarburtonQuarter::tree_depth_to_n($depth-1);
546 610 50       1080 if (! defined $n) {
547 0         0 return undef;
548             }
549 610         831 my $parts = $self->{'parts'};
550 610 100       965 if ($parts eq '2') {
551 16         45 return 2*$n - $self->{'n_start'} + $depth;
552             }
553 594 100       865 if ($parts eq '1') {
554 61         124 return $n + $depth;
555             }
556 533 50 33     1283 if ($parts eq 'octant' || $parts eq 'octant_up') {
557 0         0 return ($n + 1);
558             }
559             ### assert: $parts eq '4'
560 533         1229 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 9502 my ($self, $n) = @_;
573             ### UlamWarburton tree_n_to_depth(): $n
574              
575 168         267 $n = $n - $self->{'n_start'}; # N=0 basis
576 168 50       298 if ($n < 0) {
577 0         0 return undef;
578             }
579 168         209 $n = int($n);
580 168 100       259 if ($n == 0) {
581 16         29 return 0;
582             }
583 152 50       258 my ($depthsum) = _n0_to_depthsum_factor_rem($n, $self->{'parts'})
584             or return $n; # N=nan or +inf
585 152         475 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   754 my ($n, $parts) = @_;
674             ### _n0_to_depthsum_factor_rem(): "$n parts=$parts"
675              
676 515 50       849 my $factor = ($parts eq '4' ? 4 : $parts eq '2' ? 2 : 1);
    100          
677 515 50       806 if ($n == 0) {
678 0         0 return ([], $factor, 0);
679             }
680              
681 515         646 my $n3 = 3*$n + 1;
682 515         555 my $ndepth = 0;
683 515         590 my $power = $n3;
684 515         531 my $exp;
685 515 100       830 if ($parts eq '4') {
    50          
    50          
686 431         521 $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         930 ($power, $exp) = round_down_pow ($power, 4);
695             ### $n3
696             ### $power
697             ### $exp
698 515 50       944 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         781 my $twopow = 2**$exp;
708 515         584 my @depthsum;
709              
710 515         848 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         2162 my $nmore = $power * $factor;
716 1821 100       2544 if ($parts ne '4') { $nmore += 3*$twopow; }
  268         307  
717 1821 50       2442 if ($parts =~ /octant/) {
718             ### assert: $nmore % 2 == 0
719 0         0 $nmore = $nmore/2;
720             }
721              
722 1821         2122 my $ncmp = $ndepth + $nmore;
723             ### $nmore
724             ### $ncmp
725              
726 1821 100       2759 if ($n3 >= $ncmp) {
727             ### go to ncmp, remainder: $n3-$ncmp
728 1360         1422 $factor *= 3;
729 1360         1428 $ndepth = $ncmp;
730 1360         2728 push @depthsum, $twopow;
731             }
732             }
733              
734 515 50       877 if ($parts eq '2') {
735 0         0 $n3 += 1;
736             }
737              
738             # ### assert: ($n3 - $ndepth)%3 == 0
739 515         634 $n = ($n3 - $ndepth) / 3;
740 515         584 $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         1336 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 1511 my ($self, $level) = @_;
793 29         89 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__