File Coverage

blib/lib/Math/PlanePath/LCornerTree.pm
Criterion Covered Total %
statement 112 488 22.9
branch 61 342 17.8
condition 16 136 11.7
subroutine 14 34 41.1
pod 21 21 100.0
total 224 1021 21.9


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