File Coverage

blib/lib/Math/PlanePath/SierpinskiTriangle.pm
Criterion Covered Total %
statement 163 252 64.6
branch 61 130 46.9
condition 16 38 42.1
subroutine 25 41 60.9
pod 23 23 100.0
total 288 484 59.5


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             # Maybe:
20             #
21             # rule 22 includes the midpoint between adjacent leaf points.
22             # math-image --path=CellularRule,rule=22 --all --text
23             #
24             # rule 126 extra cell to the inward side of each
25             # math-image --path=CellularRule,rule=60 --all --text
26             #
27             # cf rule 150 double ups, something base 2 instead
28             # math-image --path=CellularRule,rule=150 --all
29             #
30             # cf rule 182 filled gaps
31             # math-image --path=CellularRule,rule=182 --all
32              
33             # math-image --path=SierpinskiTriangle --all --scale=5
34             # math-image --path=SierpinskiTriangle --all --output=numbers
35             # math-image --path=SierpinskiTriangle --all --text --size=80
36              
37             # Number of cells in a row:
38             # numerator of (2^k)/k!
39             #
40             # cf A067771 vertices of sierpinski graph, joins up replications
41             # so 1 less each giving 3*(3^k-1)/2
42             #
43              
44              
45              
46              
47             package Math::PlanePath::SierpinskiTriangle;
48 5     5   9184 use 5.004;
  5         25  
49 5     5   27 use strict;
  5         9  
  5         122  
50 5     5   25 use Carp 'croak';
  5         10  
  5         373  
51             #use List::Util 'max';
52             *max = \&Math::PlanePath::_max;
53              
54 5     5   34 use vars '$VERSION', '@ISA';
  5         9  
  5         321  
55             $VERSION = 127;
56 5     5   720 use Math::PlanePath;
  5         10  
  5         226  
57             @ISA = ('Math::PlanePath');
58             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
59              
60             use Math::PlanePath::Base::Generic
61 5         274 'is_infinite',
62 5     5   29 'round_nearest';
  5         10  
63             use Math::PlanePath::Base::Digits
64 5         459 'round_down_pow',
65             'bit_split_lowtohigh',
66 5     5   1049 'digit_join_lowtohigh';
  5         12  
67              
68             # uncomment this to run the ### lines
69             # use Smart::Comments;
70              
71 5         361 use constant parameter_info_array =>
72             [ { name => 'align',
73             share_key => 'align_trld',
74             display => 'Align',
75             type => 'enum',
76             default => 'triangular',
77             choices => ['triangular', 'right', 'left','diagonal'],
78             choices_display => ['Triangular', 'Right', 'Left','Diagonal'],
79             },
80             # { name => 'parts',
81             # share_key => 'parts_alr',
82             # display => 'Parts',
83             # type => 'enum',
84             # default => 'all',
85             # choices => ['all', 'left', 'right'],
86             # choices_display => ['All', 'Left', 'Right'],
87             # },
88             Math::PlanePath::Base::Generic::parameter_info_nstart0(),
89 5     5   34 ];
  5         12  
90              
91 5     5   32 use constant default_n_start => 0;
  5         11  
  5         245  
92 5     5   28 use constant class_y_negative => 0;
  5         10  
  5         254  
93 5     5   30 use constant n_frac_discontinuity => .5;
  5         10  
  5         276  
94 5     5   29 use constant tree_num_children_list => (0,1,2);
  5         10  
  5         1129  
95              
96             sub x_negative {
97 11     11 1 34 my ($self) = @_;
98             return ($self->{'align'} eq 'left'
99             || ($self->{'align'} eq 'triangular'
100 11   66     108 && $self->{'parts'} ne 'right'))
101             }
102             sub x_negative_at_n {
103 0     0 1 0 my ($self) = @_;
104             return ($self->{'align'} eq 'left'
105             || ($self->{'align'} eq 'triangular'
106 0 0 0     0 && $self->{'parts'} ne 'right')
107             ? $self->n_start + 1
108             : undef);
109             }
110              
111             # Note: this method shared by SierpinskiArrowhead
112             sub x_maximum {
113 0     0 1 0 my ($self) = @_;
114             return ($self->{'align'} eq 'left'
115             || ($self->{'align'} eq 'triangular'
116 0 0 0     0 && ($self->{'parts'}||'all') eq 'left')
117             ? 0 # left all X<=0
118             : undef); # others X to +infinity
119             }
120 5     5   49 use constant sumxy_minimum => 0; # triangular X>=-Y or all X>=0
  5         19  
  5         7058  
121              
122             sub diffxy_minimum {
123 0     0 1 0 my ($self) = @_;
124             return ($self->{'align'} eq 'right'
125 0 0 0     0 && $self->{'parts'} eq 'right'
126             ? 0 # X>=Y so X-Y>=0
127             : undef);
128             }
129              
130             # Note: this method shared by SierpinskiArrowhead, SierpinskiArrowheadCentres
131             sub diffxy_maximum {
132 0     0 1 0 my ($self) = @_;
133 0 0       0 return ($self->{'align'} eq 'diagonal'
134             ? undef
135             : 0); # triangular X<=Y so X-Y<=0
136             }
137              
138             sub dy_minimum {
139 0     0 1 0 my ($self) = @_;
140 0 0       0 return ($self->{'align'} eq 'diagonal' ? undef : 0);
141             }
142             sub dy_maximum {
143 0     0 1 0 my ($self) = @_;
144 0 0       0 return ($self->{'align'} eq 'diagonal' ? undef : 1);
145             }
146             {
147             my %absdx_minimum = (triangular => 1,
148             left => 1,
149             right => 0, # at N=0
150             diagonal => 0); # at N=0
151             sub absdx_minimum {
152 0     0 1 0 my ($self) = @_;
153 0         0 return $absdx_minimum{$self->{'align'}};
154             }
155             }
156             {
157             my %absdy_minimum = (triangular => 0, # rows
158             left => 0, # rows
159             right => 0, # rows
160             diagonal => 1); # diagonal always moves
161             sub absdy_minimum {
162 0     0 1 0 my ($self) = @_;
163 0         0 return $absdy_minimum{$self->{'align'}};
164             }
165             }
166              
167             sub dsumxy_minimum {
168 0     0 1 0 my ($self) = @_;
169 0 0       0 return ($self->{'align'} eq 'diagonal'
170             ? 0 # X+Y constant along diagonals
171             : undef);
172             }
173             sub dsumxy_maximum {
174 0     0 1 0 my ($self) = @_;
175 0 0       0 return ($self->{'align'} eq 'diagonal'
176             ? 1 # X+Y increase by 1 to next diagonal
177             : undef);
178             }
179              
180             sub dir_minimum_dxdy {
181 0     0 1 0 my ($self) = @_;
182 0 0       0 return ($self->{'align'} eq 'diagonal'
183             ? (0,1) # North
184             : (1,0)); # East
185             }
186             sub dir_maximum_dxdy {
187 0     0 1 0 my ($self) = @_;
188 0 0       0 return ($self->{'align'} eq 'diagonal'
189             ? (1,-1) # South-Eest
190             : (-1,0)); # supremum, West and 1 up
191             }
192              
193              
194             #------------------------------------------------------------------------------
195             my %align_known = (triangular => 1,
196             left => 1,
197             right => 1,
198             diagonal => 1);
199              
200             sub new {
201 23     23 1 2215 my $self = shift->SUPER::new(@_);
202 23 100       91 if (! defined $self->{'n_start'}) {
203 13         40 $self->{'n_start'} = $self->default_n_start;
204             }
205 23   50     190 $self->{'parts'} ||= 'all';
206              
207 23   100     98 my $align = ($self->{'align'} ||= 'triangular');
208 23 50       74 if (! $align_known{$align}) {
209 0         0 croak "Unrecognised align option: ", $align;
210             }
211             ### $align
212              
213 23         78 return $self;
214             }
215              
216             sub n_to_xy {
217 360     360 1 4476 my ($self, $n) = @_;
218             ### SierpinskiTriangle n_to_xy(): $n
219              
220             # written as $n - n_start() rather than "-=" so as to provoke an
221             # uninitialized value warning if $n==undef
222 360         650 $n = $n - $self->{'n_start'}; # N=0 basis
223              
224             # this frac behaviour slightly unspecified yet
225 360         490 my $frac;
226             {
227 360         551 my $int = int($n);
  360         530  
228 360         482 $frac = $n - $int;
229 360 50       968 if (2*$frac >= 1) { # $frac>=0.5 and BigInt friendly
    50          
230 0         0 $frac -= 1;
231 0         0 $int += 1;
232             } elsif (2*$frac < -1) { # $frac<0.5 and BigInt friendly
233 0         0 $frac += 1;
234 0         0 $int -= 1;
235             }
236 360         588 $n = $int;
237             }
238             ### $n
239             ### $frac
240              
241 360 100       654 if ($n < 0) {
242 30         77 return;
243             }
244 330 100       646 if ($n == 0) {
245 25         157 return ($n,$n);
246             }
247              
248 305 50       607 my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'})
249             or return ($n,$n); # infinite
250              
251             ### $depthbits
252             ### $ndepth
253              
254 305         554 my $zero = $n * 0;
255 305         443 $n -= $ndepth; # offset into row
256 305         661 my @nbits = bit_split_lowtohigh($n);
257              
258             # Where there's a 0-bit in the depth remains a 0-bit.
259             # Where there's a 1-bit in the depth takes a bit from Noffset.
260             # Small Noffset has less bits than the depth 1s, hence "|| 0".
261             #
262 305 100 100     642 my @xbits = map {$_ && (shift @nbits || 0)} @$depthbits;
  759         2454  
263             ### @xbits
264              
265 305         746 my $x = digit_join_lowtohigh (\@xbits, 2, $zero);
266 305         638 my $y = digit_join_lowtohigh ($depthbits, 2, $zero);
267              
268             ### n_to_xy as right: "$x,$y"
269              
270             # $x,$y is in the style of align=right, transform to others
271 305 100       992 if ($self->{'align'} eq 'left') {
    100          
    100          
272 30         51 $x -= $y;
273             } elsif ($self->{'align'} eq 'diagonal') {
274 10         17 $y -= $x;
275             } elsif ($self->{'align'} eq 'triangular') {
276 188         327 $x = 2*$x - $y;
277             }
278              
279             ### n_to_xy final: "$x,$y"
280 305         843 return ($x, $y);
281             }
282              
283             sub xy_to_n {
284 5336     5336 1 27611 my ($self, $x, $y) = @_;
285             ### SierpinskiTriangle xy_to_n(): "$x, $y"
286              
287 5336         9993 $y = round_nearest ($y);
288 5336         10137 $x = round_nearest($x);
289              
290             # transform $x,$y to the style of align=right
291 5336 100       14825 if ($self->{'align'} eq 'diagonal') {
    100          
    100          
292 17         25 $y += $x;
293             } elsif ($self->{'align'} eq 'left') {
294 513         732 $x += $y;
295             } elsif ($self->{'align'} eq 'triangular') {
296 4293         5791 $x += $y;
297 4293 100       8019 if (_divrem_mutate ($x, 2)) {
298             # if odd point
299 2120         4215 return undef;
300             }
301             }
302             ### adjusted xy: "$x,$y"
303              
304 3216         5730 return _right_xy_to_n ($self, $x, $y);
305             }
306              
307             sub _right_xy_to_n {
308 3254     3254   5965 my ($self, $x, $y) = @_;
309             ### _right_xy_to_n(): "$x, $y"
310              
311 3254 100 100     10491 unless ($x >= 0 && $x <= $y && $y >= 0) {
      66        
312             ### outside horizontal row range ...
313 1640         3545 return undef;
314             }
315 1614 50       3215 if (is_infinite($y)) {
316 0         0 return $y;
317             }
318              
319 1614         2884 my $zero = ($y * 0);
320 1614         2285 my $n = $zero; # inherit bignum 0
321 1614         2294 my $npower = $zero+1; # inherit bignum 1
322              
323 1614         3411 my @xbits = bit_split_lowtohigh($x);
324 1614         3357 my @depthbits = bit_split_lowtohigh($y);
325              
326 1614         2564 my @nbits; # N offset into row
327 1614         3242 foreach my $i (0 .. $#depthbits) { # x,y bits low to high
328 4316 100       7348 if ($depthbits[$i]) {
329 2747         3844 $n = 2*$n + $npower;
330 2747   100     7015 push @nbits, $xbits[$i] || 0; # low to high
331             } else {
332 1569 100       2824 if ($xbits[$i]) {
333 624         1946 return undef;
334             }
335             }
336 3692         5779 $npower *= 3;
337             }
338              
339             ### n at left end of y row: $n
340             ### n offset for x: @nbits
341             ### total: $n + digit_join_lowtohigh(\@nbits,2,$zero) + $self->{'n_start'}
342              
343 990         2362 return $n + digit_join_lowtohigh(\@nbits,2,$zero) + $self->{'n_start'};
344             }
345              
346             # not exact
347             sub rect_to_n_range {
348 19     19 1 2421 my ($self, $x1,$y1, $x2,$y2) = @_;
349             ### SierpinskiTriangle rect_to_n_range(): "$x1,$y1, $x2,$y2"
350              
351 19         54 $y1 = round_nearest ($y1);
352 19         39 $y2 = round_nearest ($y2);
353 19 50       40 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) }
  0         0  
354              
355 19         38 $x1 = round_nearest ($x1);
356 19         42 $x2 = round_nearest ($x2);
357 19 100       39 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) }
  9         26  
358              
359             # $y1 to $y2 is the depth range for "triangular", "right" and "left".
360             # For "diagonal" must use X+Y to reckon by anti-diagonals.
361             #
362 19 50       50 if ($self->{'align'} eq 'diagonal') {
363 0         0 $y2 += $x2;
364 0         0 $y1 += $x1;
365             }
366              
367 19 50       38 if ($y2 < 0) {
368 0         0 return (1, 0);
369             }
370 19 50       28 if ($y1 < 0) {
371 0         0 $y1 *= 0; # preserve any bignum $y1
372             }
373 19         44 return ($self->tree_depth_to_n($y1),
374             $self->tree_depth_to_n_end($y2));
375             }
376              
377             # To get N within a triangle row, based on the X range ...
378             #
379             # use Math::PlanePath::CellularRule54;
380             # *_rect_for_V = \&Math::PlanePath::CellularRule54::_rect_for_V;
381             #
382             # if ($self->{'align'} eq 'diagonal') {
383             # if ($x2 < 0 || $y2 < 0) {
384             # return (1,0);
385             # }
386             # if ($x1 < 0) { $x1 *= 0; }
387             # if ($y1 < 0) { $y1 *= 0; }
388             #
389             # return ($self->xy_to_n(0, $x1+$y1),
390             # $self->xy_to_n($x2+$y2, 0));
391             # }
392             #
393             # ($x1,$y1, $x2,$y2) = _rect_for_V ($x1,$y1, $x2,$y2)
394             # or return (1,0); # rect outside pyramid
395             #
396             # return ($self->xy_to_n($self->{'align'} eq 'right' ? 0 : -$y1,
397             # $y1),
398             # $self->xy_to_n($self->{'align'} eq 'left' ? 0 : $y2,
399             # $y2));
400              
401              
402             #------------------------------------------------------------------------------
403 5     5   45 use constant tree_num_roots => 1;
  5         11  
  5         5859  
404              
405             sub tree_n_num_children {
406 0     0 1 0 my ($self, $n) = @_;
407              
408 0         0 $n = $n - $self->{'n_start'}; # N=0 basis
409 0 0       0 if ($n < 0) {
410 0         0 return undef;
411             }
412 0 0 0     0 if ($n == 0 && $self->{'parts'} ne 'all') {
413             # parts=left or parts=right have only 1 child under the root n=0
414 0         0 return 1;
415             }
416 0 0       0 my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'})
417             or return 1; # infinite
418              
419 0 0       0 unless (shift @$depthbits) { # low bit
420             # Depth even (incl zero), two children under every point.
421 0         0 return 2;
422             }
423              
424             # Depth odd, either 0 or 1 child.
425             # If depth==1mod4 then 1-child.
426             # If depth==3mod4 so two or more trailing 1-bits then some 0-child and
427             # some 1-child.
428             #
429 0         0 $n -= $ndepth; # Noffset into row
430 0         0 my $repbit = _divrem_mutate($n,2); # low bit of $n
431 0         0 while (shift @$depthbits) { # bits of depth low to high
432 0 0       0 if (_divrem_mutate($n,2) != $repbit) { # bits of $n offset low to high
433 0         0 return 0;
434             }
435             }
436 0         0 return 1;
437             }
438              
439             sub tree_n_children {
440 44     44 1 2774 my ($self, $n) = @_;
441             ### tree_n_num_children(): $n
442              
443 44         85 $n = $n - $self->{'n_start'}; # N=0 basis
444 44 50       106 if ($n < 0) {
445 0         0 return;
446             }
447 44 50 66     97 if ($n == 0 && $self->{'parts'} ne 'all') {
448             # parts=left or parts=right have only 1 child under the root n=0
449 0         0 return ($n+1 + $self->{'n_start'});
450             }
451 44 50       96 my ($depthbits, $ndepth, $nwidth) = _n0_to_depthbits($n, $self->{'parts'})
452             or return $n; # infinite
453              
454 44         74 $n -= $ndepth; # Noffset into row
455              
456 44 100       85 if (shift @$depthbits) {
457             # Depth odd, single child under some or all points.
458             # When depth==1mod4 it's all points, when depth has more than one
459             # trailing 1-bit then it's only some points.
460 24         46 while (shift @$depthbits) { # depth==3mod4 or more low 1s
461 16         39 my $repbit = _divrem_mutate($n,2);
462 16 100       39 if (($n % 2) != $repbit) {
463 8         25 return;
464             }
465             }
466 16         58 return $n + $ndepth+$nwidth + $self->{'n_start'};
467              
468             } else {
469             # Depth even (or zero), two children under every point.
470 20         37 $n = 2*$n + $ndepth+$nwidth + $self->{'n_start'};
471 20         75 return ($n,$n+1);
472             }
473             }
474             sub tree_n_parent {
475 44     44 1 3303 my ($self, $n) = @_;
476              
477 44 50       117 my ($x,$y) = $self->n_to_xy($n)
478             or return undef;
479              
480 44 100       104 if ($self->{'align'} eq 'diagonal') {
481 11         29 my $n_parent = $self->xy_to_n($x-1, $y);
482 11 100       23 if (defined $n_parent) {
483 5         12 return $n_parent;
484             } else {
485 6         22 return $self->xy_to_n($x,$y-1);
486             }
487             }
488              
489 33         48 $y -= 1;
490 33         79 my $n_parent = $self->xy_to_n($x-($self->{'align'} ne 'left'), $y);
491 33 100       76 if (defined $n_parent) {
492 15         34 return $n_parent;
493             }
494 18         39 return $self->xy_to_n($x+($self->{'align'} ne 'right'),$y);
495             }
496              
497             sub tree_n_to_depth {
498 0     0 1 0 my ($self, $n) = @_;
499             ### SierpinskiTriangle n_to_depth(): $n
500 0         0 $n = $n - $self->{'n_start'};
501 0 0       0 if ($n < 0) {
502 0         0 return undef;
503             }
504 0 0       0 my ($depthbits) = _n0_to_depthbits($n, $self->{'parts'})
505             or return $n; # infinite
506 0         0 return digit_join_lowtohigh ($depthbits, 2, $n*0);
507             }
508             sub tree_depth_to_n {
509 38     38 1 64 my ($self, $depth) = @_;
510 38 50       85 return ($depth >= 0 ? _right_xy_to_n($self,0,$depth) : undef);
511             }
512              
513             # sub _NOTWORKING__tree_depth_to_n_range {
514             # my ($self, $depth) = @_;
515             # if (is_infinite($depth)) {
516             # return $depth;
517             # }
518             # if ($depth < 0) {
519             # return undef;
520             # }
521             #
522             # my $zero = my $n = ($depth * 0); # inherit bignum 0
523             # my $width = my $npower = $zero+1; # inherit bignum 1
524             #
525             # foreach my $dbit (bit_split_lowtohigh($depth)) {
526             # if ($dbit) {
527             # $n = 2*$n + $npower;
528             # $width *= 2;
529             # }
530             # $npower *= 3;
531             # }
532             # $n += $self->{'n_start'};
533             #
534             # return ($n, $n+$width-1);
535             # }
536              
537              
538             #------------------------------------------------------------------------------
539             # In align=diagonal style, height is following a straight line X increment
540             # until hit bit in common with Y, meaning the end of Y low 0s. Or follow
541             # straight line Y until hit bit in common with X, meaning end of X low 0s.
542             #
543             # If X,Y both even then X or Y lines are the same.
544             # If X odd then follow X to limit of Y low 0s.
545             # If Y odd then follow Y to limit of X low 0s.
546             #
547             # | 65 ...
548             # | 57 66
549             # | 49 67
550             # | 45 50 58 68
551             # | 37 69
552             # | 33 38 59 70
553             # | 29 39 51 71
554             # | 27 30 34 40 46 52 60 72
555             # | 19 73
556             # | | |
557             # | 15-20 61-74
558             # | | |
559             # | 11 21 53 75
560             # | | | | |
561             # | 9-12-16-22 47-54-62-76
562             # | | |
563             # | 5 23 41 77
564             # | | | | |
565             # | 3--6 17-24 35-42 63-78
566             # | | | | |
567             # | 1 7 13 25 31 43 55 79
568             # | | | | | | | | |
569             # | 0--2--4--8-10-14-18-26-28-32-36-44-48-56-64-80
570             # +-------------------------------------------------
571             # X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
572             #
573             # depthbits 1 0 0 0 1 Y of "right"
574             # nbits n n
575             # xbits n 0 0 0 n
576             # ybits 1-n 1-n of Y-X for "diagonal"
577             #
578             # Y odd when ylow==1,nlow==0
579             # follow its X low 0s by nbit==0 and invert of ybits==1
580             # X odd when ylow==1,nlow==1
581             # follow its Y low 0s by nbit==1 and invert of xbits=nbits==1
582             #
583             # At a given depth<=2^k can go at most to its 2^k-1 limit, which means
584             # height = 2^k-1 - depth which is depth with bits flipped.
585             # Then bits of Noffset may put it in the middle of somewhere which limits
586             # the height to a sub-part 2^j < 2^k.
587             #
588             sub tree_n_to_subheight {
589 0     0 1 0 my ($self, $n) = @_;
590             ### SierpinskiTriangle tree_n_to_subheight(): $n
591              
592 0         0 $n = $n - $self->{'n_start'};
593 0 0       0 if ($n < 0) {
594 0         0 return undef;
595             }
596 0 0       0 my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'})
597             or return $n; # infinite
598 0         0 $n -= $ndepth; # offset into row
599 0         0 my @nbits = bit_split_lowtohigh($n);
600              
601 0   0     0 my $target = $nbits[0] || 0;
602 0         0 foreach my $i (0 .. $#$depthbits) {
603 0 0       0 unless ($depthbits->[$i] ^= 1) { # flip 0<->1, at original==1 take nbit
604 0 0 0     0 if ((shift @nbits || 0) != $target) {
605 0         0 $#$depthbits = $i-1;
606 0         0 return digit_join_lowtohigh($depthbits, 2, $n*0);
607             }
608             }
609             }
610 0         0 return undef; # first or last of row, infinite
611             }
612              
613              
614             #------------------------------------------------------------------------------
615             # \ /
616             # 4 0 0 0 0 0 0 4
617             # \ / \ / \ / \ /
618             # 1 1 1 1
619             # \ / \ /
620             # 2 2 2 2
621             # \ / \ /
622             # 3 3
623             # \ /
624             # 4 0 0 4
625             # \ / \ /
626             # 1 1
627             # \ /
628             # 2 2
629             # \ /
630             # 3
631              
632             # sub _EXPERIMENTAL__tree_n_to_leafdist {
633             # my ($self, $n) = @_;
634             # ### _EXPERIMENTAL__tree_n_to_leafdist() ...
635             # my $d = $self->tree_n_to_depth($n);
636             # if (defined $d) {
637             # $d = 3 - ($d % 4);
638             # if ($d == 0 && $self->tree_n_num_children($n) != 0) {
639             # $d = 4;
640             # }
641             # }
642             # return $d;
643             # }
644             sub _EXPERIMENTAL__tree_n_to_leafdist {
645 0     0   0 my ($self, $n) = @_;
646             ### _EXPERIMENTAL__tree_n_to_leafdist(): $n
647              
648 0         0 $n = $n - $self->{'n_start'}; # N=0 basis
649 0 0       0 if ($n < 0) {
650 0         0 return undef;
651             }
652 0 0       0 my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'}) # low to high
653             or return 1; # infinite
654             ### $depthbits
655              
656             # depth bits leafdist
657             # 0 0,0 3
658             # 1 0,1 2
659             # 2 1,0 1
660             # 3 1,1 0 or 4
661             #
662 0   0     0 my $ret = 3 - ((shift @$depthbits)||0);
663 0 0       0 if (shift @$depthbits) { $ret -= 2; }
  0         0  
664             ### $ret
665 0 0       0 if ($ret) {
666 0         0 return $ret;
667             }
668              
669 0         0 $n -= $ndepth;
670             ### Noffset into row: $n
671              
672             # Low bits of Nrem unchanging while trailing 1-bits in @depthbits,
673             # to distinguish between leaf or non-leaf. Same as tree_n_children().
674             #
675 0         0 my $repbit = _divrem_mutate($n,2); # low bit of $n
676             ### $repbit
677 0         0 do {
678             ### next bit: $n%2
679 0 0       0 if (_divrem_mutate($n,2) != $repbit) { # bits of $n offset low to high
680 0         0 return 0; # is a leaf
681             }
682             } while (shift @$depthbits);
683 0         0 return 4; # is a non-leaf
684             }
685              
686             #------------------------------------------------------------------------------
687             # Return ($depthbits, $ndepth, $nwidth).
688             # $depthbits is an arrayref of bits low to high which are the tree depth of $n.
689             # $ndepth is first N of the row.
690             # $nwidth is the number of points in the row.
691             #
692             sub _n0_to_depthbits {
693 349     349   615 my ($n, $parts) = @_;
694             ### _n0_to_depthbits(): "$n $parts"
695              
696 349 50       763 if (is_infinite($n)) {
697 0         0 return;
698             }
699 349 100       825 if ($n == 0) {
700 4         15 return ([], 0, 1);
701             }
702              
703 345 50       1148 my ($nwidth, $bitpos) = round_down_pow ($parts eq 'all' ? $n : 2*$n-1,
704             3);
705             ### $nwidth
706             ### $bitpos
707              
708 345         561 my @depthbits;
709 345         667 $depthbits[$bitpos] = 1;
710 345 50       687 my $ndepth = ($parts eq 'all' ? $nwidth : ($nwidth + 1)/2);
711 345         504 $nwidth *= 2;
712              
713 345         710 while (--$bitpos >= 0) {
714 494         704 $nwidth /= 3;
715             ### at: "n=$n nwidth=$nwidth bitpos=$bitpos depthbits=".join(',',map{$_||0}@depthbits)
716              
717 494 100       884 if ($n >= $ndepth + $nwidth) {
718 240         368 $depthbits[$bitpos] = 1;
719 240         322 $ndepth += $nwidth;
720 240         462 $nwidth *= 2;
721             } else {
722 254         495 $depthbits[$bitpos] = 0;
723             }
724             }
725              
726             # Nwidth = 2**count1bits(depth), when parts=all
727             ### @depthbits
728             ### assert: $parts ne 'all' || $nwidth == (1 << scalar(grep{$_}@depthbits))
729              
730 345         1097 return (\@depthbits, $ndepth, $nwidth);
731             }
732              
733             #------------------------------------------------------------------------------
734             # levels
735              
736 5     5   2220 use Math::PlanePath::SierpinskiArrowheadCentres;
  5         16  
  5         368  
737             *level_to_n_range = \&Math::PlanePath::SierpinskiArrowheadCentres::level_to_n_range;
738             *n_to_level = \&Math::PlanePath::SierpinskiArrowheadCentres::n_to_level;
739              
740             #-----------------------------------------------------------------------------
741             1;
742             __END__