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, 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             # 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   9513 use 5.004;
  5         55  
49 5     5   30 use strict;
  5         12  
  5         121  
50 5     5   25 use Carp 'croak';
  5         20  
  5         352  
51             #use List::Util 'max';
52             *max = \&Math::PlanePath::_max;
53              
54 5     5   32 use vars '$VERSION', '@ISA';
  5         10  
  5         356  
55             $VERSION = 129;
56 5     5   735 use Math::PlanePath;
  5         23  
  5         273  
57             @ISA = ('Math::PlanePath');
58             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
59              
60             use Math::PlanePath::Base::Generic
61 5         273 'is_infinite',
62 5     5   32 'round_nearest';
  5         9  
63             use Math::PlanePath::Base::Digits
64 5         472 'round_down_pow',
65             'bit_split_lowtohigh',
66 5     5   1091 'digit_join_lowtohigh';
  5         12  
67              
68             # uncomment this to run the ### lines
69             # use Smart::Comments;
70              
71 5         320 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   35 ];
  5         10  
90              
91 5     5   70 use constant default_n_start => 0;
  5         18  
  5         234  
92 5     5   38 use constant class_y_negative => 0;
  5         10  
  5         258  
93 5     5   33 use constant n_frac_discontinuity => .5;
  5         10  
  5         229  
94 5     5   29 use constant tree_num_children_list => (0,1,2);
  5         8  
  5         1192  
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     112 && $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   46 use constant sumxy_minimum => 0; # triangular X>=-Y or all X>=0
  5         10  
  5         6850  
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 2360 my $self = shift->SUPER::new(@_);
202 23 100       95 if (! defined $self->{'n_start'}) {
203 13         48 $self->{'n_start'} = $self->default_n_start;
204             }
205 23   50     130 $self->{'parts'} ||= 'all';
206              
207 23   100     89 my $align = ($self->{'align'} ||= 'triangular');
208 23 50       71 if (! $align_known{$align}) {
209 0         0 croak "Unrecognised align option: ", $align;
210             }
211             ### $align
212              
213 23         73 return $self;
214             }
215              
216             sub n_to_xy {
217 360     360 1 4651 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         639 $n = $n - $self->{'n_start'}; # N=0 basis
223              
224             # this frac behaviour slightly unspecified yet
225 360         514 my $frac;
226             {
227 360         515 my $int = int($n);
  360         569  
228 360         500 $frac = $n - $int;
229 360 50       949 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         668 $n = $int;
237             }
238             ### $n
239             ### $frac
240              
241 360 100       661 if ($n < 0) {
242 30         63 return;
243             }
244 330 100       637 if ($n == 0) {
245 25         66 return ($n,$n);
246             }
247              
248 305 50       634 my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'})
249             or return ($n,$n); # infinite
250              
251             ### $depthbits
252             ### $ndepth
253              
254 305         525 my $zero = $n * 0;
255 305         458 $n -= $ndepth; # offset into row
256 305         647 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     612 my @xbits = map {$_ && (shift @nbits || 0)} @$depthbits;
  759         2542  
263             ### @xbits
264              
265 305         766 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       950 if ($self->{'align'} eq 'left') {
    100          
    100          
272 30         53 $x -= $y;
273             } elsif ($self->{'align'} eq 'diagonal') {
274 10         15 $y -= $x;
275             } elsif ($self->{'align'} eq 'triangular') {
276 188         314 $x = 2*$x - $y;
277             }
278              
279             ### n_to_xy final: "$x,$y"
280 305         917 return ($x, $y);
281             }
282              
283             sub xy_to_n {
284 5336     5336 1 27938 my ($self, $x, $y) = @_;
285             ### SierpinskiTriangle xy_to_n(): "$x, $y"
286              
287 5336         9832 $y = round_nearest ($y);
288 5336         10389 $x = round_nearest($x);
289              
290             # transform $x,$y to the style of align=right
291 5336 100       14412 if ($self->{'align'} eq 'diagonal') {
    100          
    100          
292 17         27 $y += $x;
293             } elsif ($self->{'align'} eq 'left') {
294 513         736 $x += $y;
295             } elsif ($self->{'align'} eq 'triangular') {
296 4293         5995 $x += $y;
297 4293 100       8092 if (_divrem_mutate ($x, 2)) {
298             # if odd point
299 2120         4180 return undef;
300             }
301             }
302             ### adjusted xy: "$x,$y"
303              
304 3216         5921 return _right_xy_to_n ($self, $x, $y);
305             }
306              
307             sub _right_xy_to_n {
308 3254     3254   5452 my ($self, $x, $y) = @_;
309             ### _right_xy_to_n(): "$x, $y"
310              
311 3254 100 100     10402 unless ($x >= 0 && $x <= $y && $y >= 0) {
      66        
312             ### outside horizontal row range ...
313 1640         3202 return undef;
314             }
315 1614 50       3409 if (is_infinite($y)) {
316 0         0 return $y;
317             }
318              
319 1614         2843 my $zero = ($y * 0);
320 1614         2251 my $n = $zero; # inherit bignum 0
321 1614         2300 my $npower = $zero+1; # inherit bignum 1
322              
323 1614         3319 my @xbits = bit_split_lowtohigh($x);
324 1614         3401 my @depthbits = bit_split_lowtohigh($y);
325              
326 1614         2563 my @nbits; # N offset into row
327 1614         3478 foreach my $i (0 .. $#depthbits) { # x,y bits low to high
328 4316 100       7491 if ($depthbits[$i]) {
329 2747         3949 $n = 2*$n + $npower;
330 2747   100     7345 push @nbits, $xbits[$i] || 0; # low to high
331             } else {
332 1569 100       2830 if ($xbits[$i]) {
333 624         1937 return undef;
334             }
335             }
336 3692         5988 $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         2318 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 2559 my ($self, $x1,$y1, $x2,$y2) = @_;
349             ### SierpinskiTriangle rect_to_n_range(): "$x1,$y1, $x2,$y2"
350              
351 19         48 $y1 = round_nearest ($y1);
352 19         37 $y2 = round_nearest ($y2);
353 19 50       42 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) }
  0         0  
354              
355 19         35 $x1 = round_nearest ($x1);
356 19         36 $x2 = round_nearest ($x2);
357 19 100       40 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) }
  9         19  
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       45 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       38 if ($y1 < 0) {
371 0         0 $y1 *= 0; # preserve any bignum $y1
372             }
373 19         42 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   44 use constant tree_num_roots => 1;
  5         9  
  5         6113  
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 3073 my ($self, $n) = @_;
441             ### tree_n_num_children(): $n
442              
443 44         81 $n = $n - $self->{'n_start'}; # N=0 basis
444 44 50       148 if ($n < 0) {
445 0         0 return;
446             }
447 44 50 66     109 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       101 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       118 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         49 while (shift @$depthbits) { # depth==3mod4 or more low 1s
461 16         37 my $repbit = _divrem_mutate($n,2);
462 16 100       39 if (($n % 2) != $repbit) {
463 8         25 return;
464             }
465             }
466 16         61 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         78 return ($n,$n+1);
472             }
473             }
474             sub tree_n_parent {
475 44     44 1 3386 my ($self, $n) = @_;
476              
477 44 50       99 my ($x,$y) = $self->n_to_xy($n)
478             or return undef;
479              
480 44 100       97 if ($self->{'align'} eq 'diagonal') {
481 11         27 my $n_parent = $self->xy_to_n($x-1, $y);
482 11 100       22 if (defined $n_parent) {
483 5         13 return $n_parent;
484             } else {
485 6         13 return $self->xy_to_n($x,$y-1);
486             }
487             }
488              
489 33         48 $y -= 1;
490 33         83 my $n_parent = $self->xy_to_n($x-($self->{'align'} ne 'left'), $y);
491 33 100       69 if (defined $n_parent) {
492 15         32 return $n_parent;
493             }
494 18         43 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 66 my ($self, $depth) = @_;
510 38 50       79 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   620 my ($n, $parts) = @_;
694             ### _n0_to_depthbits(): "$n $parts"
695              
696 349 50       780 if (is_infinite($n)) {
697 0         0 return;
698             }
699 349 100       777 if ($n == 0) {
700 4         23 return ([], 0, 1);
701             }
702              
703 345 50       994 my ($nwidth, $bitpos) = round_down_pow ($parts eq 'all' ? $n : 2*$n-1,
704             3);
705             ### $nwidth
706             ### $bitpos
707              
708 345         576 my @depthbits;
709 345         822 $depthbits[$bitpos] = 1;
710 345 50       677 my $ndepth = ($parts eq 'all' ? $nwidth : ($nwidth + 1)/2);
711 345         494 $nwidth *= 2;
712              
713 345         682 while (--$bitpos >= 0) {
714 494         726 $nwidth /= 3;
715             ### at: "n=$n nwidth=$nwidth bitpos=$bitpos depthbits=".join(',',map{$_||0}@depthbits)
716              
717 494 100       889 if ($n >= $ndepth + $nwidth) {
718 240         363 $depthbits[$bitpos] = 1;
719 240         354 $ndepth += $nwidth;
720 240         457 $nwidth *= 2;
721             } else {
722 254         503 $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         1131 return (\@depthbits, $ndepth, $nwidth);
731             }
732              
733             #------------------------------------------------------------------------------
734             # levels
735              
736 5     5   2351 use Math::PlanePath::SierpinskiArrowheadCentres;
  5         12  
  5         424  
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__