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