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, 2019 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   9465 use 5.004;
  5         24  
49 5     5   27 use strict;
  5         9  
  5         175  
50 5     5   30 use Carp 'croak';
  5         8  
  5         352  
51             #use List::Util 'max';
52             *max = \&Math::PlanePath::_max;
53              
54 5     5   34 use vars '$VERSION', '@ISA';
  5         12  
  5         348  
55             $VERSION = 128;
56 5     5   691 use Math::PlanePath;
  5         11  
  5         253  
57             @ISA = ('Math::PlanePath');
58             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
59              
60             use Math::PlanePath::Base::Generic
61 5         265 'is_infinite',
62 5     5   33 'round_nearest';
  5         9  
63             use Math::PlanePath::Base::Digits
64 5         477 'round_down_pow',
65             'bit_split_lowtohigh',
66 5     5   1036 'digit_join_lowtohigh';
  5         19  
67              
68             # uncomment this to run the ### lines
69             # use Smart::Comments;
70              
71 5         327 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   33 ];
  5         13  
90              
91 5     5   30 use constant default_n_start => 0;
  5         9  
  5         220  
92 5     5   26 use constant class_y_negative => 0;
  5         10  
  5         272  
93 5     5   34 use constant n_frac_discontinuity => .5;
  5         11  
  5         222  
94 5     5   26 use constant tree_num_children_list => (0,1,2);
  5         8  
  5         1194  
95              
96             sub x_negative {
97 11     11 1 37 my ($self) = @_;
98             return ($self->{'align'} eq 'left'
99             || ($self->{'align'} eq 'triangular'
100 11   66     116 && $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   36 use constant sumxy_minimum => 0; # triangular X>=-Y or all X>=0
  5         19  
  5         6677  
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 2238 my $self = shift->SUPER::new(@_);
202 23 100       97 if (! defined $self->{'n_start'}) {
203 13         53 $self->{'n_start'} = $self->default_n_start;
204             }
205 23   50     138 $self->{'parts'} ||= 'all';
206              
207 23   100     89 my $align = ($self->{'align'} ||= 'triangular');
208 23 50       79 if (! $align_known{$align}) {
209 0         0 croak "Unrecognised align option: ", $align;
210             }
211             ### $align
212              
213 23         85 return $self;
214             }
215              
216             sub n_to_xy {
217 360     360 1 4527 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         621 $n = $n - $self->{'n_start'}; # N=0 basis
223              
224             # this frac behaviour slightly unspecified yet
225 360         489 my $frac;
226             {
227 360         506 my $int = int($n);
  360         523  
228 360         509 $frac = $n - $int;
229 360 50       937 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         595 $n = $int;
237             }
238             ### $n
239             ### $frac
240              
241 360 100       657 if ($n < 0) {
242 30         68 return;
243             }
244 330 100       640 if ($n == 0) {
245 25         72 return ($n,$n);
246             }
247              
248 305 50       614 my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'})
249             or return ($n,$n); # infinite
250              
251             ### $depthbits
252             ### $ndepth
253              
254 305         542 my $zero = $n * 0;
255 305         436 $n -= $ndepth; # offset into row
256 305         706 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     601 my @xbits = map {$_ && (shift @nbits || 0)} @$depthbits;
  759         2487  
263             ### @xbits
264              
265 305         754 my $x = digit_join_lowtohigh (\@xbits, 2, $zero);
266 305         648 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       901 if ($self->{'align'} eq 'left') {
    100          
    100          
272 30         48 $x -= $y;
273             } elsif ($self->{'align'} eq 'diagonal') {
274 10         16 $y -= $x;
275             } elsif ($self->{'align'} eq 'triangular') {
276 188         302 $x = 2*$x - $y;
277             }
278              
279             ### n_to_xy final: "$x,$y"
280 305         871 return ($x, $y);
281             }
282              
283             sub xy_to_n {
284 5336     5336 1 27146 my ($self, $x, $y) = @_;
285             ### SierpinskiTriangle xy_to_n(): "$x, $y"
286              
287 5336         9918 $y = round_nearest ($y);
288 5336         10094 $x = round_nearest($x);
289              
290             # transform $x,$y to the style of align=right
291 5336 100       14560 if ($self->{'align'} eq 'diagonal') {
    100          
    100          
292 17         25 $y += $x;
293             } elsif ($self->{'align'} eq 'left') {
294 513         711 $x += $y;
295             } elsif ($self->{'align'} eq 'triangular') {
296 4293         5845 $x += $y;
297 4293 100       8307 if (_divrem_mutate ($x, 2)) {
298             # if odd point
299 2120         4163 return undef;
300             }
301             }
302             ### adjusted xy: "$x,$y"
303              
304 3216         5898 return _right_xy_to_n ($self, $x, $y);
305             }
306              
307             sub _right_xy_to_n {
308 3254     3254   5417 my ($self, $x, $y) = @_;
309             ### _right_xy_to_n(): "$x, $y"
310              
311 3254 100 100     10548 unless ($x >= 0 && $x <= $y && $y >= 0) {
      66        
312             ### outside horizontal row range ...
313 1640         3302 return undef;
314             }
315 1614 50       3194 if (is_infinite($y)) {
316 0         0 return $y;
317             }
318              
319 1614         2840 my $zero = ($y * 0);
320 1614         2216 my $n = $zero; # inherit bignum 0
321 1614         2294 my $npower = $zero+1; # inherit bignum 1
322              
323 1614         3316 my @xbits = bit_split_lowtohigh($x);
324 1614         3481 my @depthbits = bit_split_lowtohigh($y);
325              
326 1614         2635 my @nbits; # N offset into row
327 1614         3539 foreach my $i (0 .. $#depthbits) { # x,y bits low to high
328 4316 100       7168 if ($depthbits[$i]) {
329 2747         3855 $n = 2*$n + $npower;
330 2747   100     7115 push @nbits, $xbits[$i] || 0; # low to high
331             } else {
332 1569 100       2897 if ($xbits[$i]) {
333 624         1850 return undef;
334             }
335             }
336 3692         5852 $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         2281 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 2428 my ($self, $x1,$y1, $x2,$y2) = @_;
349             ### SierpinskiTriangle rect_to_n_range(): "$x1,$y1, $x2,$y2"
350              
351 19         62 $y1 = round_nearest ($y1);
352 19         40 $y2 = round_nearest ($y2);
353 19 50       41 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) }
  0         0  
354              
355 19         38 $x1 = round_nearest ($x1);
356 19         36 $x2 = round_nearest ($x2);
357 19 100       39 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) }
  9         18  
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       34 if ($y2 < 0) {
368 0         0 return (1, 0);
369             }
370 19 50       36 if ($y1 < 0) {
371 0         0 $y1 *= 0; # preserve any bignum $y1
372             }
373 19         40 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   41 use constant tree_num_roots => 1;
  5         10  
  5         5892  
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 2763 my ($self, $n) = @_;
441             ### tree_n_num_children(): $n
442              
443 44         89 $n = $n - $self->{'n_start'}; # N=0 basis
444 44 50       103 if ($n < 0) {
445 0         0 return;
446             }
447 44 50 66     101 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       117 my ($depthbits, $ndepth, $nwidth) = _n0_to_depthbits($n, $self->{'parts'})
452             or return $n; # infinite
453              
454 44         72 $n -= $ndepth; # Noffset into row
455              
456 44 100       93 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         48 while (shift @$depthbits) { # depth==3mod4 or more low 1s
461 16         36 my $repbit = _divrem_mutate($n,2);
462 16 100       37 if (($n % 2) != $repbit) {
463 8         24 return;
464             }
465             }
466 16         63 return $n + $ndepth+$nwidth + $self->{'n_start'};
467              
468             } else {
469             # Depth even (or zero), two children under every point.
470 20         38 $n = 2*$n + $ndepth+$nwidth + $self->{'n_start'};
471 20         76 return ($n,$n+1);
472             }
473             }
474             sub tree_n_parent {
475 44     44 1 3265 my ($self, $n) = @_;
476              
477 44 50       105 my ($x,$y) = $self->n_to_xy($n)
478             or return undef;
479              
480 44 100       93 if ($self->{'align'} eq 'diagonal') {
481 11         26 my $n_parent = $self->xy_to_n($x-1, $y);
482 11 100       21 if (defined $n_parent) {
483 5         11 return $n_parent;
484             } else {
485 6         14 return $self->xy_to_n($x,$y-1);
486             }
487             }
488              
489 33         51 $y -= 1;
490 33         81 my $n_parent = $self->xy_to_n($x-($self->{'align'} ne 'left'), $y);
491 33 100       69 if (defined $n_parent) {
492 15         31 return $n_parent;
493             }
494 18         38 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 63 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   611 my ($n, $parts) = @_;
694             ### _n0_to_depthbits(): "$n $parts"
695              
696 349 50       745 if (is_infinite($n)) {
697 0         0 return;
698             }
699 349 100       774 if ($n == 0) {
700 4         23 return ([], 0, 1);
701             }
702              
703 345 50       1004 my ($nwidth, $bitpos) = round_down_pow ($parts eq 'all' ? $n : 2*$n-1,
704             3);
705             ### $nwidth
706             ### $bitpos
707              
708 345         577 my @depthbits;
709 345         609 $depthbits[$bitpos] = 1;
710 345 50       650 my $ndepth = ($parts eq 'all' ? $nwidth : ($nwidth + 1)/2);
711 345         511 $nwidth *= 2;
712              
713 345         728 while (--$bitpos >= 0) {
714 494         789 $nwidth /= 3;
715             ### at: "n=$n nwidth=$nwidth bitpos=$bitpos depthbits=".join(',',map{$_||0}@depthbits)
716              
717 494 100       900 if ($n >= $ndepth + $nwidth) {
718 240         353 $depthbits[$bitpos] = 1;
719 240         342 $ndepth += $nwidth;
720 240         461 $nwidth *= 2;
721             } else {
722 254         487 $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         1061 return (\@depthbits, $ndepth, $nwidth);
731             }
732              
733             #------------------------------------------------------------------------------
734             # levels
735              
736 5     5   2156 use Math::PlanePath::SierpinskiArrowheadCentres;
  5         14  
  5         350  
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__