File Coverage

blib/lib/Math/PlanePath/LCornerTree.pm
Criterion Covered Total %
statement 113 487 23.2
branch 61 342 17.8
condition 16 136 11.7
subroutine 14 33 42.4
pod 21 21 100.0
total 225 1019 22.0


line stmt bran cond sub pod time code
1             # Copyright 2012, 2013, 2014 Kevin Ryde
2              
3             # This file is part of Math-PlanePath-Toothpick.
4             #
5             # Math-PlanePath-Toothpick is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as published
7             # by the Free Software Foundation; either version 3, or (at your option) any
8             # later version.
9             #
10             # Math-PlanePath-Toothpick is distributed in the hope that it will be
11             # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13             # Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-PlanePath-Toothpick. If not, see .
17              
18              
19             # parts=2 row total X = 3^count1bits(depth) = A048883
20             # parts=wedge row total X = (3^count1bits(depth) + 1) / 2
21              
22              
23             # A147562 U-W
24             # A160410
25             # A151920
26              
27             #------------------------------------------------------------------------------
28             # cf A183126 triplet around each exposed end, starting from length=1 two ends
29             # A183127 added 0,1,6,16,16,40,16,40,40,112,16
30             # http://www.math.vt.edu/people/layman/sequences/A183126.htm
31             # added = 4 * 3^count1bits(depth) or thereabouts
32             # two diagonal halves
33             #
34             #------------------------------------------------------------------------------
35             # wedge odd/even
36              
37             # 1 1
38             # 2 0 1
39             # 2 2
40              
41             # 3 3 3 3
42             # 3 2 2 2 2 3
43             # 3 2 1 1 2
44             # 3 3 0 1 2
45             # 3 2 2 3
46             # 3 3 3 3
47              
48             # 4 4 4 4 4 4 4 4
49             # 4 3 3 4 4 3 3 4
50             # 3 2 2 2 2 3 4
51             # 3 2 1 1 2 4 4
52             # 4 3 3 0 1 2 4 4
53             # 4 4 3 2 2 3 4
54             # 4 3 3 3 3 4
55             # 4 4 4 4
56             #
57              
58             #------------------------------------------------------------------------------
59              
60              
61             package Math::PlanePath::LCornerTree;
62 2     2   1570 use 5.004;
  2         5  
  2         65  
63 2     2   8 use strict;
  2         2  
  2         62  
64 2     2   8 use Carp 'croak';
  2         2  
  2         124  
65             #use List::Util 'max';
66             *max = \&Math::PlanePath::_max;
67              
68 2     2   9 use vars '$VERSION', '@ISA';
  2         2  
  2         137  
69             $VERSION = 16;
70 2     2   1356 use Math::PlanePath;
  2         8451  
  2         71  
71             @ISA = ('Math::PlanePath');
72              
73              
74             use Math::PlanePath::Base::Generic
75 2         80 'is_infinite',
76 2     2   12 'round_nearest';
  2         2  
77             use Math::PlanePath::Base::Digits
78 2         102 'round_down_pow',
79             'bit_split_lowtohigh',
80             'digit_split_lowtohigh',
81 2     2   433 'digit_join_lowtohigh';
  2         1152  
82              
83             # uncomment this to run the ### lines
84             # use Smart::Comments;
85              
86              
87 2     2   10 use constant n_start => 0;
  2         2  
  2         150  
88 2         8658 use constant parameter_info_array =>
89             [ { name => 'parts',
90             share_key => 'parts_lcornertree',
91             display => 'Parts',
92             type => 'enum',
93             default => '4',
94             choices => ['4','3','2','1',
95             'octant','octant+1','octant_up','octant_up+1',
96             'wedge','wedge+1','diagonal','diagonal-1',
97             ],
98             choices_display => ['4','3','2','1',
99             'Octant','Octant+1','Octant Up','Octant Up+1',
100             'Wedge','Wedge+1','Diagonal','Diagonal 1',
101             ],
102             description => 'Which parts of the plane to fill.',
103             },
104 2     2   9 ];
  2         2  
105              
106             {
107             my %x_negative = (4 => 1,
108             3 => 1,
109             2 => 1,
110             1 => 0,
111             octant => 0,
112             'octant+1' => 0,
113             octant_up => 0,
114             'octant_up+1' => 0,
115             wedge => 1,
116             'wedge+1' => 1,
117             diagonal => 1,
118             'diagonal-1' => 1,
119             );
120             sub x_negative {
121 0     0 1 0 my ($self) = @_;
122 0         0 return $x_negative{$self->{'parts'}};
123             }
124             }
125             {
126             my %y_negative = (4 => 1,
127             3 => 1,
128             2 => 0,
129             1 => 0,
130             octant => 0,
131             'octant+1' => 0,
132             octant_up => 0,
133             'octant_up+1' => 0,
134             wedge => 0,
135             'wedge+1' => 0,
136             diagonal => 1,
137             'diagonal-1' => 1,
138             );
139             sub y_negative {
140 0     0 1 0 my ($self) = @_;
141 0         0 return $y_negative{$self->{'parts'}};
142             }
143             }
144              
145             {
146             my %x_negative_at_n = (4 => 1,
147             3 => 2,
148             2 => 1,
149             1 => undef,
150             octant => undef,
151             'octant+1' => undef,
152             octant_up => undef,
153             'octant_up+1' => undef,
154             wedge => 1,
155             'wedge+1' => 1,
156             diagonal => 2,
157             'diagonal-1' => 11,
158             );
159             sub x_negative_at_n {
160 0     0 1 0 my ($self) = @_;
161 0         0 return $x_negative_at_n{$self->{'parts'}};
162             }
163             }
164             {
165             my %y_negative_at_n = (4 => 2,
166             3 => 1,
167             2 => undef,
168             1 => undef,
169             octant => undef,
170             'octant+1' => undef,
171             octant_up => undef,
172             'octant_up+1' => undef,
173             wedge => undef,
174             'wedge+1' => undef,
175             diagonal => 0,
176             'diagonal-1' => 4,
177             );
178             sub y_negative_at_n {
179 0     0 1 0 my ($self) = @_;
180 0         0 return $y_negative_at_n{$self->{'parts'}};
181             }
182             }
183              
184             {
185             my %sumxy_minimum = (1 => 0,
186             octant => 0,
187             'octant+1' => 0,
188             octant_up => 0,
189             'octant_up+1' => 0,
190             wedge => -1, # X>=-Y-1 so X+Y>=-1
191             'wedge+1' => -2, # X>=-Y-2 so X+Y>=-2
192             diagonal => -1,
193             'diagonal-1' => 0, # X>=-Y
194             );
195             sub sumxy_minimum {
196 0     0 1 0 my ($self) = @_;
197 0         0 return $sumxy_minimum{$self->{'parts'}};
198             }
199             }
200             {
201             my %diffxy_minimum = (octant => 0, # octant X>=Y so X-Y>=0
202             'octant+1' => -1, # octant X>=Y-1 so X-Y>=-1
203             );
204             sub diffxy_minimum {
205 0     0 1 0 my ($self) = @_;
206 0         0 return $diffxy_minimum{$self->{'parts'}};
207             }
208             }
209             {
210             my %diffxy_maximum = (octant_up => 0, # octant_up X<=Y so X-Y<=0
211             'octant_up+1' => 1, # octant_up+1 X<=Y+1 so X-Y<=1
212             wedge => 0, # wedge X<=Y so X-Y<=0
213             'wedge+1' => 1, # wedge+1 X>=Y+1 so X-Y>=1
214             );
215             sub diffxy_maximum {
216 0     0 1 0 my ($self) = @_;
217 0         0 return $diffxy_maximum{$self->{'parts'}};
218             }
219             }
220              
221             # parts=1 Dir4 max 12,-11
222             # 121,-110
223             # 303,-213
224             # 1212,-1031
225             # 12121,-10310 -> 12,-10
226             # 30303,-21213 -> 3,-2
227             # parts=2 dX=big,dY=-1
228             # parts=3 dX=big,dY=-1
229             # parts=4 dx=0,dy=-1 at N=1
230             {
231             my %dir_maximum_dxdy = (1 => [3,-2], # supremum
232             2 => [0,0], # supremum
233             3 => [0,0], # supremum
234             4 => [0,-1], # N=1 dX=0,dY=-1 South
235             octant => [0,-2], # N=4 dX=0,dY=-2 South
236             'octant+1' => [1,-2], # N=6 dX=1,dY=-2 SSE
237             octant_up => [0,-1], # N=8 dX=0,dY=-1 South
238             'octant_up+1' => [0,-1], # N=11 dX=0,dY=-1 South
239             wedge => [0,-1], # N=13 dX=0,dY=-1 South
240             'wedge+1' => [0,-1], # N=6 dX=0,dY=-1 South
241             diagonal => [2,-2], # N=2 South-East
242             'diagonal-1' => [3,-3], # N=12 South-East
243             );
244             sub dir_maximum_dxdy {
245 0     0 1 0 my ($self) = @_;
246 0         0 return @{$dir_maximum_dxdy{$self->{'parts'}}};
  0         0  
247             }
248             }
249              
250             {
251             my %any_num_children_2
252             = (octant => 1,
253             octant_up => 1,
254             wedge => 1,
255             diagonal => 1,
256             );
257             sub tree_num_children_list {
258 0     0 1 0 my ($self) = @_;
259 0 0       0 return (0,
260             ($any_num_children_2{$self->{'parts'}} ? (2) : ()),
261             3);
262             }
263             }
264              
265             #------------------------------------------------------------------------------
266              
267             # how many toplevel root nodes in the tree of given $parts
268             my %parts_to_numroots = (4 => 4,
269             3 => 3,
270             2 => 2,
271             1 => 1,
272             octant => 1,
273             'octant+1' => 1,
274             octant_up => 1,
275             'octant_up+1' => 1,
276             wedge => 2,
277             'wedge+1' => 2,
278             diagonal => 3,
279             'diagonal-1' => 1,
280             );
281             sub tree_num_roots {
282 0     0 1 0 my ($self) = @_;
283 0         0 return $parts_to_numroots{$self->{'parts'}};
284             }
285              
286             sub new {
287 13     13 1 1672 my $self = shift->SUPER::new(@_);
288 13   100     197 my $parts = ($self->{'parts'} ||= 4);
289 13 50       53 if (! exists $parts_to_numroots{$parts}) {
290 0         0 croak "Unrecognised parts: ",$parts;
291             }
292 13         28 return $self;
293             }
294              
295             my @next_state = (0,12,0,4, 4,0,4,8, 8,4,8,12, 12,8,12,0);
296             my @digit_to_x = (0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1);
297             my @digit_to_y = (0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1);
298              
299             my @diagonal1_n_to_xy = ([0,0],
300             [1,0],
301             [1,1],
302             [0,1]);
303              
304             sub n_to_xy {
305 0     0 1 0 my ($self, $n) = @_;
306             ### LCornerTree n_to_xy(): $n
307              
308 0 0       0 if ($n < 0) { return; }
  0         0  
309 0 0       0 if (is_infinite($n)) { return ($n,$n); }
  0         0  
310             {
311 0         0 my $int = int($n);
  0         0  
312             ### $int
313             ### $n
314 0 0       0 if ($n != $int) {
315 0         0 my ($x1,$y1) = $self->n_to_xy($int);
316 0         0 my ($x2,$y2) = $self->n_to_xy($int+1);
317 0         0 my $frac = $n - $int; # inherit possible BigFloat
318 0         0 my $dx = $x2-$x1;
319 0         0 my $dy = $y2-$y1;
320 0         0 return ($frac*$dx + $x1, $frac*$dy + $y1);
321             }
322 0         0 $n = $int; # BigFloat int() gives BigInt, use that
323             }
324              
325 0 0       0 if (is_infinite($n)) { return ($n,$n); }
  0         0  
326 0         0 my $zero = ($n * 0); # inherit bignum 0
327 0         0 my $parts = $self->{'parts'};
328              
329 0 0       0 if ($parts eq 'diagonal-1') {
330 0 0       0 if ($n <= 3) {
331 0         0 return @{$diagonal1_n_to_xy[$n]};
  0         0  
332             }
333             }
334              
335 0         0 my ($depthbits, $ndepth, $nwidth) = _n0_to_depthbits($n, $parts);
336              
337             ### $n
338             ### $ndepth
339             ### $nwidth
340             ### $parts
341             ### $depthbits
342             ### assert: $nwidth == $self->tree_depth_to_n(1+digit_join_lowtohigh($depthbits,2,$n*0)) - $self->tree_depth_to_n(digit_join_lowtohigh($depthbits,2,$n*0))
343              
344 0         0 $n -= $ndepth;
345             ### N remainder offset into row: $n
346             ### assert: $n >= 0
347             ### assert: $n < $nwidth
348              
349 0         0 my $quad;
350 0         0 my $x = 0;
351 0         0 my $y = 0;
352 0 0       0 if ($parts eq 'wedge') {
    0          
    0          
    0          
    0          
353             ### assert: $nwidth % 2 == 0
354 0         0 my $noct = $nwidth/2;
355 0 0       0 if ($n < $noct) {
356 0         0 $n += $noct - 1;
357 0         0 $quad = 0;
358             } else {
359 0         0 $n -= $noct;
360 0         0 $quad = 1;
361             }
362             } elsif ($parts eq 'wedge+1') {
363             ### assert: $nwidth % 2 == 0
364 0         0 my $noct = $nwidth/2;
365 0 0       0 if ($n < $noct) {
366             ### first half, add to N: $noct - 3
367 0         0 $n += $noct - 3;
368 0         0 $quad = 0;
369             } else {
370             ### second half ...
371 0         0 $n -= $noct;
372 0         0 $quad = 1;
373             }
374              
375             } elsif ($parts eq 'diagonal') {
376             ### assert: ($nwidth+1)%4 == 0
377 0         0 my $noct = ($nwidth+1)/4;
378             ### $noct
379 0 0       0 if ($n < $noct) {
    0          
380             ### first oct is quad=3 ...
381 0         0 $n += $noct - 1;
382 0         0 $quad = 3;
383             } elsif ($n >= $nwidth - $noct) {
384             ### last oct is quad=1 ...
385 0         0 $n -= ($nwidth - $noct);
386 0         0 $quad = 1;
387             } else {
388 0         0 $n -= $noct;
389 0         0 $quad = 0;
390             }
391              
392             } elsif ($parts eq 'diagonal-1') {
393             ### assert: ($nwidth+3) % 4 == 0
394 0         0 my $noct = ($nwidth+3)/4;
395             ### $noct
396 0 0       0 if ($n < $noct) {
    0          
397             ### first oct is quad=3 ...
398 0         0 $n += $noct - 3;
399 0         0 $quad = 3;
400 0         0 $x = -1;
401 0         0 $y = 1;
402             } elsif ($n >= $nwidth - $noct) {
403             ### last oct is quad=1 ...
404 0         0 $n -= ($nwidth - $noct);
405 0         0 $quad = 1;
406 0         0 $x = 1;
407 0         0 $y = -1;
408             } else {
409 0         0 $n -= $noct;
410 0         0 $quad = 0;
411 0         0 $x = 1;
412 0         0 $y = 1;
413             }
414              
415             } elsif ((my $numroots = $parts_to_numroots{$parts}) > 1) {
416             # like a mixed-radix high digit radix $numroots then rest radix 3
417 0         0 $nwidth /= $numroots;
418 0         0 ($quad, $n) = _divrem($n,$nwidth);
419             ### $quad
420             ### assert: $quad >= 0
421             ### assert: $quad < $numroots
422 0 0       0 if ($parts eq '3') {
423 0 0       0 if ($quad == 1) { $quad = 3; } # quad=1 -> 3
  0         0  
424 0 0       0 if ($quad == 2) { $quad = 1; } # quad=2 -> 1
  0         0  
425             }
426             } else {
427 0         0 $quad = 0;
428 0 0       0 if ($parts eq 'octant_up') {
    0          
429 0         0 $n += $nwidth - 1;
430             } elsif ($parts eq 'octant_up+1') {
431 0         0 $n += $nwidth - 3;
432             }
433             }
434             ### $quad
435             ### $n
436              
437 0         0 my @nternary = digit_split_lowtohigh($n, 3);
438             ### @nternary
439              
440             # Ternary digits for triple parts of Noffset mapped out to base4 digits in
441             # the style of LCornerReplicate.
442             # Where there's a 0-bit in the depth is a 0-digit for Nbase4.
443             # Where there's a 1-bit in the depth takes a ternary+1 for Nbase4.
444             # Small Noffset has less trits than the depth 1s, hence "nternary || 0".
445             #
446 0 0 0     0 my @nbase4 = map {$_ && (1 + (shift @nternary || 0))} @$depthbits;
  0         0  
447             ### @nbase4
448              
449 0         0 my $state = 0;
450 0         0 my (@xbits, @ybits);
451 0         0 foreach my $i (reverse 0 .. $#nbase4) { # digits high to low
452 0         0 $state += $nbase4[$i];
453 0         0 $xbits[$i] = $digit_to_x[$state];
454 0         0 $ybits[$i] = $digit_to_y[$state];
455 0         0 $state = $next_state[$state];
456             }
457              
458             ### xbits join: digit_join_lowtohigh (\@xbits, 2, $zero)
459             ### ybits join: digit_join_lowtohigh (\@ybits, 2, $zero)
460 0         0 $x += digit_join_lowtohigh (\@xbits, 2, $zero);
461 0         0 $y += digit_join_lowtohigh (\@ybits, 2, $zero);
462              
463 0 0       0 if ($quad & 1) {
464 0         0 ($x,$y) = (-1-$y,$x); # rotate +90
465             }
466 0 0       0 if ($quad & 2) {
467 0         0 $x = -1-$x; # rotate +180
468 0         0 $y = -1-$y;
469             }
470             ### final: "$x,$y"
471 0         0 return $x,$y;
472             }
473              
474             # my @next_state = (0, 1, 3, 2,
475             # my @yx_to_digit = (0, 1, 3, 2,
476             # 0, 1, 3, 2, # rot +90
477             # );
478              
479             sub xy_to_n {
480 0     0 1 0 my ($self, $x, $y) = @_;
481             ### LCornerTree xy_to_n(): "$x, $y"
482              
483 0         0 $x = round_nearest ($x);
484 0         0 $y = round_nearest ($y);
485              
486 0         0 my $parts = $self->{'parts'};
487 0         0 my $quad = 0;
488              
489 0 0       0 if ($parts eq '3') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
490 0 0       0 if ($x < 0) {
491 0 0       0 if ($y < 0) {
492 0         0 return undef;
493             }
494 0         0 ($x,$y) = ($y,-1-$x); # rotate -90 and offset
495 0         0 $quad = 2;
496             } else {
497 0 0       0 if ($y < 0) {
498 0         0 ($x,$y) = (-1-$y,$x); # rotate +90 and offset
499 0         0 $quad = 1;
500             }
501             }
502              
503             } elsif ($parts eq 'octant') {
504 0 0 0     0 if ($y < 0 || $y > $x) { return undef; }
  0         0  
505             } elsif ($parts eq 'octant+1') {
506 0 0 0     0 if ($x < 0 || $y < 0 || $y > $x+1) { return undef; }
  0   0     0  
507              
508             } elsif ($parts eq 'octant_up') {
509 0 0 0     0 if ($x < 0 || $x > $y) { return undef; }
  0         0  
510             } elsif ($parts eq 'octant_up+1') {
511 0 0 0     0 if ($y < 0 || $x < 0 || $x > $y+1) { return undef; }
  0   0     0  
512              
513             } elsif ($parts eq 'wedge') {
514 0 0 0     0 if ($x < -1-$y || $x > $y) { return undef; }
  0         0  
515 0 0       0 if ($x < 0) {
516 0         0 ($x,$y) = ($y,-1-$x); # rotate -90 and offset
517 0         0 $quad = 1;
518             }
519             } elsif ($parts eq 'wedge+1') {
520 0 0 0     0 if ($y < 0 || $x < -2-$y || $x > $y+1) { return undef; }
  0   0     0  
521 0 0       0 if ($x < 0) {
522 0         0 ($x,$y) = ($y,-1-$x); # rotate -90 and offset
523 0         0 $quad = 1;
524             }
525              
526             } elsif ($parts eq 'diagonal') {
527 0 0       0 if ($x < -1-$y) { return undef; } # must have X+Y>=-1
  0         0  
528 0 0       0 if ($y < 0) {
    0          
529             ### lower triangular Y neg ...
530 0         0 ($x,$y) = (-1-$y,$x); # rotate +90 and offset
531             } elsif ($x < 0) {
532             ### left triangular X neg ...
533 0         0 ($x,$y) = ($y,-1-$x); # rotate -90 and offset
534 0         0 $quad = 2;
535             } else {
536             ### first quad ...
537 0         0 $quad = 1;
538             }
539              
540             } elsif ($parts eq 'diagonal-1') {
541 0 0 0     0 if ($x == 0 && $y == 0) {
542 0         0 return 0;
543             }
544 0 0       0 if ($x < -$y) { return undef; } # must have X+Y>=0
  0         0  
545 0 0       0 if ($y <= 0) {
    0          
546             ### lower triangular Y<=0 ...
547 0         0 ($x,$y) = (-$y,$x-1); # rotate +90 and offset
548             } elsif ($x <= 0) {
549             ### left triangular X<=0 ...
550 0         0 ($x,$y) = ($y-1,-$x); # rotate -90 and offset
551 0         0 $quad = 2;
552             } else {
553             ### first quad ...
554 0         0 $x -= 1;
555 0         0 $y -= 1;
556 0         0 $quad = 1;
557             }
558              
559             } else {
560             # parts=1,2,4
561              
562 0 0       0 if ($y < 0) {
563 0 0       0 if ($parts ne '4') {
564 0         0 return undef;
565             }
566 0         0 $x = -1-$x; # rotate +180
567 0         0 $y = -1-$y;
568 0         0 $quad = 2;
569             }
570              
571 0 0       0 if ($x < 0) {
572 0 0       0 if ($parts eq '1') {
573 0         0 return undef;
574             }
575 0         0 ($x,$y) = ($y,-1-$x); # rotate +90 and offset
576 0         0 $quad++;
577             }
578             }
579             ### $quad
580             ### xy rotated into first quad: "$x,$y"
581              
582 0 0       0 if (is_infinite($x)) {
583 0         0 return $x;
584             }
585 0 0       0 if (is_infinite($y)) {
586 0         0 return $y;
587             }
588              
589 0         0 my $zero = ($x * 0 * $y); # inherit bignum 0
590             # my @xbits = bit_split_lowtohigh($x);
591             # my @ybits = bit_split_lowtohigh($y);
592             # my $exp = max($#xbits, $#ybits);
593             # my $len = 2**$exp;
594              
595 0         0 my ($len,$exp) = round_down_pow(max($x,$y), 2);
596 0         0 my @depthbits;
597             my @ndigits; # high to low
598              
599 0         0 foreach my $i (reverse 0 .. $exp) {
600             ### at: "x=$x,y=$y ndigits=".join(',',@ndigits)." len=$len"
601              
602             ### assert: $x >= 0
603             ### assert: $y >= 0
604             ### assert: $x < 2 * $len
605             ### assert: $y < 2 * $len
606             ### assert: $len == int($len)
607              
608 0 0 0     0 if ($depthbits[$i] = ($x >= $len || $y >= $len ? 1 : 0)) {
    0          
609             # one of the three parts away from the origin
610              
611 0 0       0 if ($y < $len) {
    0          
612             ### lower right, digit 0 ...
613 0         0 ($x,$y) = ($len-1-$y,$x-$len); # rotate +90 and offset
614 0         0 push @ndigits, 0;
615             } elsif ($x >= $len) {
616             ### diagonal, digit 1 ...
617             ### right, digit 1 ...
618 0         0 $x -= $len;
619 0         0 $y -= $len;
620 0         0 push @ndigits, 1;
621             } else {
622             ### top left, digit 2 ...
623 0         0 ($x,$y) = ($y-$len,$len-1-$x); # rotate -90 and offset
624 0         0 push @ndigits, 2;
625             }
626             }
627              
628 0         0 $len /= 2;
629             }
630              
631 0         0 @ndigits = reverse @ndigits;
632 0         0 my $n = digit_join_lowtohigh(\@ndigits,3,$zero);
633             ### $n
634             ### $quad
635              
636 0 0       0 if ($quad) {
637             ### npower: 3**scalar(@ndigits)
638             ### quad npower: $quad * 3**scalar(@ndigits)
639 0         0 $n += $quad * 3**scalar(@ndigits);
640             }
641 0 0 0     0 if ($parts eq 'octant_up' || $parts eq 'octant_up+1'
      0        
      0        
      0        
      0        
642             || $parts eq 'wedge' || $parts eq 'wedge+1'
643             || $parts eq 'diagonal' || $parts eq 'diagonal-1') {
644 0         0 $n -= (3**scalar(@ndigits) - 1) / 2;
645             }
646              
647 0         0 my $depth = digit_join_lowtohigh(\@depthbits,2,$zero);
648 0 0       0 if ($parts eq 'diagonal-1') {
649 0 0       0 if ($depth) { $n += 1; }
  0         0  
650 0         0 $depth += 1;
651             }
652 0 0       0 if ($parts eq 'octant_up+1') {
    0          
653 0 0       0 if ($depth) { $n += 1; }
  0         0  
654             } elsif ($parts eq 'wedge+1') {
655 0 0       0 if ($depth) { $n += 1; }
  0         0  
656             }
657              
658             ### @depthbits
659             ### $depth
660 0         0 $n += $self->tree_depth_to_n($depth);
661              
662             ### final n: $n
663 0         0 return $n;
664             }
665              
666              
667             # not exact
668             sub rect_to_n_range {
669 0     0 1 0 my ($self, $x1,$y1, $x2,$y2) = @_;
670             ### LCornerTree rect_to_n_range(): "$x1,$y1 $x2,$y2"
671              
672 0         0 $x1 = round_nearest ($x1);
673 0         0 $x2 = round_nearest ($x2);
674 0         0 $y1 = round_nearest ($y1);
675 0         0 $y2 = round_nearest ($y2);
676              
677 0 0       0 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
678 0 0       0 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
679              
680 0         0 my $parts = $self->{'parts'};
681 0         0 my $xymax;
682 0 0       0 if ($parts eq 'octant') {
    0          
    0          
    0          
    0          
    0          
683 0 0 0     0 if ($y2 < 0 || $x2 < $y1) { return (1,0); }
  0         0  
684 0         0 $xymax = $x2;
685             } elsif ($parts eq 'octant+1') {
686 0 0 0     0 if ($x2 < 0 || $y2 < 0 || $y1 > $x2+1) { return (1,0); }
  0   0     0  
687 0         0 $xymax = $x2+1;
688              
689             } elsif ($parts eq 'octant_up') {
690 0 0 0     0 if ($x2 < 0 || $y2 < $x1) { return (1,0); }
  0         0  
691 0         0 $xymax = $y2;
692             } elsif ($parts eq 'octant_up+1') {
693 0 0 0     0 if ($x2 < 0 || $y2 < 0 || $x1 > $y2+1) { return (1,0); }
  0   0     0  
694 0         0 $xymax = $y2+1;
695              
696             } elsif ($parts eq 'wedge') {
697 0 0 0     0 if ($x2 < -1-$y2 || $x1 > $y2) { return (1,0); }
  0         0  
698 0         0 $xymax = $y2;
699             } elsif ($parts eq 'wedge+1') {
700 0 0 0     0 if ($x2 < -2-$y2 || $x1 > $y2+1) { return (1,0); }
  0         0  
701 0         0 $xymax = $y2+1;
702              
703             } else {
704 0         0 $xymax = max($x2,$y2);
705 0 0       0 if ($parts eq 'diagonal') {
    0          
    0          
706 0 0       0 if ($x2 < -1-$y2) { return (1,0); }
  0         0  
707              
708             } elsif ($parts eq 'diagonal-1') {
709 0 0       0 if ($x2 < -$y2) { return (1,0); }
  0         0  
710              
711             } elsif ($parts eq '1') {
712 0 0 0     0 if ($x2 < 0 || $y2 < 0) { return (1,0); }
  0         0  
713              
714             } else {
715             # parts=2,3,4
716 0         0 $xymax = max($xymax, -1-$x1);
717              
718 0 0       0 if ($parts eq '2') {
719 0 0       0 if ($y2 < 0) { return (1,0); }
  0         0  
720             } else {
721             # parts=3,4
722 0         0 $xymax = max($xymax, -1-$y1);
723              
724 0 0       0 if ($parts eq '3') {
725 0 0 0     0 if ($x2 < 0 && $y2 < 0) { return (1,0); }
  0         0  
726             }
727             }
728             }
729             }
730             ### $xymax
731              
732 0         0 my ($depth) = round_down_pow($xymax,2);
733 0         0 $depth *= 2;
734 0 0       0 if ($parts eq 'diagonal-1') {
735 0         0 $depth += 1;
736             }
737             ### depth: 2*$xymax
738 0         0 return (0,
739             $self->tree_depth_to_n($depth));
740             }
741              
742             #------------------------------------------------------------------------------
743              
744             # quad(d) = sum i=0tod 3^count1bits(i)
745             # quad(d) = 2*oct(d) + d
746             # oct(d) = (quad(d) + d) / 2
747             # oct(d) = sum i=0tod (3^count1bits(d) + 1)/2
748             # quad add 1,3,3, 9, 3, 9, 9,27, 3, 9, 9,27,9,27,27,81 A048883
749             # oct add 1,2,2, 5, 2, 5, 5,14, 2, 5, 5,14,5,14,14,41, A162784
750             # oct total 1,3,5,10,12,17,22,36,38,43,48,
751             #
752             sub tree_depth_to_n {
753 62     62 1 3279 my ($self, $depth) = @_;
754             ### tree_depth_to_n(): "depth=$depth"
755              
756 62 50       150 if ($depth < 0) { return undef; }
  0         0  
757 62 50       218 if (is_infinite($depth)) { return $depth; }
  0         0  
758              
759 62         531 my $parts = $self->{'parts'};
760 62 100       117 if ($parts eq 'diagonal-1') {
761 10 100       16 if ($depth <= 1) {
762 2         3 return $depth;
763             }
764 8         7 $depth -= 1;
765             }
766              
767             # pow3 = 3^count1bits(depth)
768 60         112 my $n = ($depth*0); # inherit bignum 0
769 60         67 my $pow3 = $n + 1; # inherit bignum 1
770              
771 60         162 foreach my $bit (reverse bit_split_lowtohigh($depth)) { # high to low
772 141         1023 $n *= 4;
773 141 100       269 if ($bit) {
774 86         73 $n += $pow3;
775 86         131 $pow3 *= 3;
776             }
777             }
778              
779 60 100 66     561 if ($parts eq 'octant' || $parts eq 'octant_up') {
    50 33        
    100          
    50          
    100          
    100          
780 13         21 $n = ($n + $depth) / 2;
781             } elsif ($parts eq 'octant+1' || $parts eq 'octant_up+1') {
782 0         0 $n = ($n + 3*$depth) / 2;
783 0 0       0 if ($depth) { $n -= 1; }
  0         0  
784             } elsif ($parts eq 'wedge') {
785 9         14 $n += $depth;
786             } elsif ($parts eq 'wedge+1') {
787 0         0 $n += 3*$depth;
788 0 0       0 if ($depth) { $n -= 2; }
  0         0  
789             } elsif ($parts eq 'diagonal') {
790 9         14 $n = 2*$n + $depth;
791             } elsif ($parts eq 'diagonal-1') {
792 8         41 $n = 2*$n + 3*$depth - 1;
793             } else {
794 21         25 $n *= $parts;
795             }
796              
797 60         215 return $n;
798             }
799              
800             sub tree_n_to_depth {
801 158     158 1 11563 my ($self, $n) = @_;
802             ### LCornerTree n_to_xy(): $n
803              
804 158 50       424 if ($n < 0) { return undef; }
  0         0  
805 158         221 $n = int($n);
806 158 50       434 if (is_infinite($n)) {
807 0         0 return $n;
808             }
809              
810 158 100       1472 if ($self->{'parts'} eq 'diagonal-1') {
811 19 100       27 if ($n == 0) { return 0; }
  2         3  
812             }
813              
814 156         437 my ($depthbits) = _n0_to_depthbits ($n, $self->{'parts'});
815 156         742 my $depth = digit_join_lowtohigh ($depthbits, 2, $n*0);
816 156 100       2175 if ($self->{'parts'} eq 'diagonal-1') {
817 17         17 $depth += 1;
818             }
819 156         418 return $depth;
820             }
821              
822             # nwidth = 4^k next 4^(k-1) or to 3*4^(k-1)
823             #
824             # octant nwidth = (4^k + 2^k)/2
825             # = 2^k*(2^k+1)/2
826             # next (4^(k-1) + 2^(k-1))/2
827             # = 2^(k-1)*(2^(k-1) + 1)/2
828             #
829             # 0 1 2 4 6 8 12 16
830             # oct 0,1,4,7,13,16,22,28,43,46,52,58,73,79,94,109,151,154,160,166,
831             # oct+1 0,1,3,5,10,12,17,22,36,38,43,48,62,67,81, 95,136,138,143,148,
832             #
833             # wedge+1 0,2,8,14,26,32,44,56,86,92,104,116,146,158,188,218,302,308,320,332,
834             #
835             sub _n0_to_depthbits {
836 156     156   196 my ($n, $parts) = @_;
837             ### _n0_to_depthbits(): $n
838             ### $parts
839              
840 156         296 my $numroots = $parts_to_numroots{$parts};
841 156 100       313 if ($n < $numroots) {
842             ### root point, depth=0 ...
843 18         63 return ([], 0, $numroots); # $n is in row depth=0
844             }
845              
846 138         136 my $ndepth = 0;
847 138         148 my ($nmore, $nhalf, $bitpos);
848 138 100 66     1024 if ($parts eq 'octant' || $parts eq 'octant_up') {
    50 33        
    100          
    50          
    100          
    100          
849 32         124 ($nmore, $bitpos) = round_down_pow (2*$n, 4);
850 32         571 $nhalf = 2**$bitpos;
851             } elsif ($parts eq 'octant+1' || $parts eq 'octant_up+1') {
852 0         0 ($nmore, $bitpos) = round_down_pow (2*$n, 4);
853 0         0 $nhalf = 2**$bitpos;
854 0         0 $ndepth = -1;
855              
856             } elsif ($parts eq 'wedge') {
857 28         87 ($nmore, $bitpos) = round_down_pow ($n, 4);
858 28         422 $nhalf = 2**$bitpos;
859             } elsif ($parts eq 'wedge+1') {
860 0         0 ($nmore, $bitpos) = round_down_pow ($n, 4);
861 0         0 $nhalf = 2**$bitpos;
862 0         0 $ndepth = -2;
863              
864             } elsif ($parts eq 'diagonal') {
865 15         68 ($nmore, $bitpos) = round_down_pow ($n/2, 4);
866 15         196 $nmore *= 2;
867 15         18 $nhalf = 2**$bitpos;
868              
869             } elsif ($parts eq 'diagonal-1') {
870 17 100       22 if ($n <= 3) {
871             ### n in row depth=1 points ...
872 2         4 return ([], 1, 3);
873             }
874 15         28 ($nmore, $bitpos) = round_down_pow ($n, 4);
875 15         113 $nmore *= 2;
876 15         19 $nhalf = 2**$bitpos;
877 15         11 $ndepth = -1;
878              
879             } else {
880 46         254 ($nmore, $bitpos) = round_down_pow ($n/$numroots, 4);
881 46         502 $nmore *= $parts;
882 46         59 $nhalf = 0;
883             }
884             ### $nmore
885             ### $nhalf
886             ### $bitpos
887              
888 136         142 my @depthbits;
889 136         134 for (;;) {
890             ### at: "n=$n ndepth=$ndepth nmore=$nmore nhalf=$nhalf bitpos=$bitpos depthbits=".join(',',map{$_||0}@depthbits)
891              
892 347         282 my $ncmp;
893 347 100 100     1605 if ($parts eq 'wedge' || $parts eq 'diagonal') {
    50          
    100          
    100          
894 109         148 $ncmp = $ndepth + $nmore + $nhalf;
895             } elsif ($parts eq 'wedge+1') {
896 0         0 $ncmp = $ndepth + $nmore + 3*$nhalf;
897             } elsif ($parts eq 'diagonal-1') {
898 48         45 $ncmp = $ndepth + $nmore + 3*$nhalf;
899             } elsif ($nhalf) { # octant, octant_up
900 84         142 $ncmp = $ndepth + ($nmore + $nhalf)/2;
901 84 50 33     352 if ($parts eq 'octant+1' || $parts eq 'octant_up+1') {
902 0         0 $ncmp += $nhalf;
903             }
904             } else {
905 106         121 $ncmp = $ndepth + $nmore;
906             }
907             ### $ncmp
908              
909 347 100       473 if ($n >= $ncmp) {
910             ### use this depthbit ...
911 214         430 $depthbits[$bitpos] = 1;
912 214         179 $ndepth = $ncmp;
913 214         217 $nmore *= 3;
914             } else {
915 133         192 $depthbits[$bitpos] = 0;
916             }
917 347         292 $bitpos--;
918 347 100       708 last unless $bitpos >= 0;
919              
920 211         193 $nmore /= 4;
921 211         226 $nhalf /= 2;
922             }
923              
924             # Nwidth = 3**count1bits(depth)
925             ### final ...
926             ### $nmore
927             ### $nhalf
928             ### @depthbits
929             # except for parts=diagonal
930             # ### assert: $nmore == $numroots * 3 ** (scalar(grep{$_}@depthbits))
931              
932 136 100 100     1147 if ($parts eq 'wedge' || $parts eq 'diagonal') {
    50 33        
    100          
    50          
    100          
933 43         100 $nmore += 1;
934             } elsif ($parts eq 'wedge+1') {
935 0         0 $nmore += 3;
936             } elsif ($parts eq 'diagonal-1') {
937 15         11 $nmore += 3;
938             } elsif ($parts eq 'octant+1' || $parts eq 'octant_up+1') {
939 0         0 $nmore = ($nmore + 3) / 2;
940             } elsif ($nhalf) {
941             ### assert: $nmore % 2 == 1
942 32         58 $nmore = ($nmore + 1) / 2;
943             }
944              
945 136         609 return (\@depthbits, $ndepth, $nmore);
946             }
947              
948             # ENHANCE-ME: step by the bits, not by X,Y
949             # ENHANCE-ME: tree_n_to_depth() by probe?
950             my @surround8_dx = (1, 0, -1, 0, 1, -1, 1, -1);
951             my @surround8_dy = (0, 1, 0, -1, 1, 1, -1, -1);
952             sub tree_n_children {
953 0     0 1 0 my ($self, $n) = @_;
954             ### LCornerTree tree_n_children(): $n
955              
956 0 0       0 if ($n < 0) {
957             ### before n_start ...
958 0         0 return;
959             }
960 0         0 my ($x,$y) = $self->n_to_xy($n);
961 0         0 my @n_children;
962 0         0 foreach my $i (0 .. 7) {
963 0 0       0 if (defined (my $n_surround = $self->xy_to_n($x + $surround8_dx[$i],
964             $y + $surround8_dy[$i]))) {
965             ### $n_surround
966 0 0       0 if ($n_surround > $n) {
967 0         0 my $n_parent = $self->tree_n_parent($n_surround);
968             ### $n_parent
969 0 0 0     0 if (defined $n_parent && $n_parent == $n) {
970 0         0 push @n_children, $n_surround;
971             }
972             }
973             }
974             }
975             ### @n_children
976             # ### assert: scalar(@n_children) == 0 || scalar(@n_children) == 3
977 0         0 return sort {$a<=>$b} @n_children;
  0         0  
978             }
979              
980             sub tree_n_parent {
981 0     0 1 0 my ($self, $n) = @_;
982             ### LCornerTree tree_n_parent(): $n
983              
984 0         0 my $want_depth = $self->tree_n_to_depth($n);
985 0 0 0     0 if (! defined $want_depth || ($want_depth -= 1) < 0) {
986 0         0 return undef;
987             }
988 0         0 my ($x,$y) = $self->n_to_xy($n);
989             ### $want_depth
990              
991 0         0 foreach my $i (0 .. 7) {
992 0 0       0 if (defined (my $n_surround = $self->xy_to_n($x + $surround8_dx[$i],
993             $y + $surround8_dy[$i]))) {
994 0         0 my $depth_surround = $self->tree_n_to_depth($n_surround);
995             ### $n_surround
996             ### $depth_surround
997 0 0       0 if ($depth_surround == $want_depth) {
998 0         0 return $n_surround;
999             }
1000             }
1001             }
1002             ### no parent ...
1003 0         0 return undef;
1004             }
1005              
1006             sub tree_n_to_subheight {
1007 0     0 1 0 my ($self, $n) = @_;
1008             ### LCornerTree tree_n_to_subheight(): $n
1009              
1010 0 0       0 if ($n < 0) { return undef; }
  0         0  
1011 0 0       0 if (is_infinite($n)) { return $n; }
  0         0  
1012              
1013 0         0 my ($depthbits, $ndepth, $nwidth) = _n0_to_depthbits($n, $self->{'parts'});
1014 0         0 $n -= $ndepth; # remaining offset into row
1015              
1016 0         0 my $parts = $self->{'parts'};
1017 0 0       0 if ($parts eq 'octant+1') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1018 0 0 0     0 if ($ndepth > 0 && $n == $nwidth-1) {
1019 0         0 return 0; # last point in each row doesn't grow, except N=0
1020             }
1021              
1022             } elsif ($parts eq 'octant_up') {
1023 0         0 $n += $nwidth - 1; # add to be second half of parts=1 row
1024              
1025             } elsif ($parts eq 'octant_up+1') {
1026 0 0       0 if ($n == 0) {
1027 0 0       0 return ($ndepth == 0
1028             ? undef # N=0 is infinite
1029             : 0); # first of any other row doesn't grow at all
1030             }
1031 0         0 $n += $nwidth - 3; # add to be second half of parts=1 row
1032              
1033             } elsif ($parts eq 'wedge') {
1034             # swap row halves into style of parts=1
1035 0         0 my $noct = $nwidth/2;
1036 0 0       0 if ($n < $noct) {
1037 0         0 $n += $noct-1;
1038             } else {
1039 0         0 $n -= $noct;
1040             }
1041              
1042             } elsif ($parts eq 'wedge+1') {
1043 0 0 0     0 if ($ndepth > 0 && ($n == 0 || $n == $nwidth-1)) {
      0        
1044 0         0 return 0; # first and last points don't grow, except N=0,N=1
1045             }
1046             # swap row halves into style of parts=1
1047 0         0 my $noct = $nwidth/2;
1048 0 0       0 if ($n < $noct) {
1049 0         0 $n += $noct-3;
1050             } else {
1051 0         0 $n -= $noct;
1052             }
1053              
1054             } elsif ($parts eq 'diagonal') {
1055             # swap row ends into style of parts=1
1056 0         0 my $noct = ($nwidth+1)/4;
1057 0 0       0 if ($n < $noct) {
    0          
1058 0         0 $n += $noct-1;
1059             } elsif ($n >= $nwidth - $noct) {
1060 0         0 $n -= ($nwidth - $noct);
1061             } else {
1062 0         0 $n -= $noct;
1063             }
1064              
1065             } elsif ($parts eq 'diagonal-1') {
1066             # swap row ends into style of parts=1
1067 0 0       0 if (@$depthbits < 2) {
1068             # N=0to3 depth=0,depth=1 on diagonals so infinite subtree
1069 0         0 return undef;
1070             }
1071 0 0 0     0 if ($n == 0 || $n == $nwidth-1) {
1072             # first and last of row are extras which never grow
1073 0         0 return 0;
1074             }
1075 0         0 my $noct = ($nwidth+3)/4;
1076 0 0       0 if ($n < $noct) {
    0          
1077 0         0 $n += $noct - 3;
1078             } elsif ($n >= $nwidth - $noct) {
1079 0         0 $n -= ($nwidth - $noct);
1080             } else {
1081 0         0 $n -= $noct;
1082             }
1083              
1084             } elsif ((my $numroots = $parts_to_numroots{$parts}) > 1) {
1085             # parts=2,3,4 reduce to parts=1 style
1086             ### assert: $nwidth % $numroots == 0
1087 0         0 $nwidth /= $numroots;
1088 0         0 $n %= $nwidth; # Nrem in level, as per n_to_xy()
1089             }
1090              
1091             ### $depthbits
1092             ### $n
1093              
1094 0         0 foreach my $i (0 .. $#$depthbits) {
1095             ### $i
1096             ### N ternary digit: $n%3
1097 0 0       0 unless ($depthbits->[$i] ^= 1) { # invert, taken Nrem digit at bit=1
1098 0 0       0 if (_divrem_mutate($n,3) != 1) { # stop at lowest non-"1" ternary digit
1099 0         0 $#$depthbits = $i; # truncate
1100 0         0 return digit_join_lowtohigh($depthbits, 2, $n*0);
1101             }
1102             }
1103             }
1104 0         0 return undef; # Nrem all 1-digits, so on central infinite spine
1105             }
1106              
1107             # return ($quotient, $remainder)
1108             sub _divrem {
1109 0     0   0 my ($n, $d) = @_;
1110 0 0 0     0 if (ref $n && $n->isa('Math::BigInt')) {
1111 0         0 my ($quot,$rem) = $n->copy->bdiv($d);
1112 0 0 0     0 if (! ref $d || $d < 1_000_000) {
1113 0         0 $rem = $rem->numify; # plain remainder if fits
1114             }
1115 0         0 return ($quot, $rem);
1116             }
1117 0         0 my $rem = $n % $d;
1118 0         0 return (int(($n-$rem)/$d), # exact division stays in UV
1119             $rem);
1120             }
1121              
1122             # return $remainder, modify $n
1123             # the scalar $_[0] is modified, but if it's a BigInt then a new BigInt is made
1124             # and stored there, the bigint value is not changed
1125             sub _divrem_mutate {
1126 0     0   0 my $d = $_[1];
1127 0         0 my $rem;
1128 0 0 0     0 if (ref $_[0] && $_[0]->isa('Math::BigInt')) {
1129 0         0 ($_[0], $rem) = $_[0]->copy->bdiv($d); # quot,rem in array context
1130 0 0 0     0 if (! ref $d || $d < 1_000_000) {
1131 0         0 return $rem->numify; # plain remainder if fits
1132             }
1133             } else {
1134 0         0 $rem = $_[0] % $d;
1135 0         0 $_[0] = int(($_[0]-$rem)/$d); # exact division stays in UV
1136             }
1137 0         0 return $rem;
1138             }
1139              
1140             #------------------------------------------------------------------------------
1141             # levels
1142              
1143             sub level_to_n_range {
1144 7     7 1 268 my ($self, $level) = @_;
1145 7         19 return (0, $self->tree_depth_to_n_end(2**$level-1));
1146             }
1147             sub n_to_level {
1148 0     0 1   my ($self, $n) = @_;
1149 0           my $depth = $self->tree_n_to_depth($n);
1150 0 0         if (! defined $depth) { return undef; }
  0            
1151 0           my ($pow, $exp) = round_down_pow ($depth, 2);
1152 0           return $exp + 1;
1153             }
1154              
1155             #------------------------------------------------------------------------------
1156             1;
1157             __END__