File Coverage

blib/lib/Math/PlanePath/HTree.pm
Criterion Covered Total %
statement 35 214 16.3
branch 0 80 0.0
condition 0 33 0.0
subroutine 12 24 50.0
pod 12 12 100.0
total 59 363 16.2


line stmt bran cond sub pod time code
1             # Copyright 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             package Math::PlanePath::HTree;
20 1     1   1239 use 5.004;
  1         3  
  1         35  
21 1     1   5 use strict;
  1         2  
  1         53  
22             #use List::Util 'max';
23             *max = \&Math::PlanePath::_max;
24              
25             use Math::PlanePath::Base::Generic
26 1         58 'is_infinite',
27 1     1   479 'round_nearest';
  1         656  
28             use Math::PlanePath::Base::Digits
29 1         64 'round_down_pow',
30             'bit_split_lowtohigh',
31 1     1   448 'digit_join_lowtohigh';
  1         1646  
32              
33 1     1   588 use Math::PlanePath::LCornerTree;
  1         3  
  1         55  
34             *_divrem = \&Math::PlanePath::LCornerTree::_divrem;
35              
36 1     1   5 use vars '$VERSION', '@ISA';
  1         1  
  1         52  
37             $VERSION = 16;
38 1     1   4 use Math::PlanePath;
  1         1  
  1         144  
39             @ISA = ('Math::PlanePath');
40              
41             # uncomment this to run the ### lines
42             # use Smart::Comments;
43              
44              
45             # return $remainder, modify $n
46             # the scalar $_[0] is modified, but if it's a BigInt then a new BigInt is made
47             # and stored there, the bigint value is not changed
48             sub _divrem_mutate {
49 0     0   0 my $d = $_[1];
50 0         0 my $rem;
51 0 0 0     0 if (ref $_[0] && $_[0]->isa('Math::BigInt')) {
52 0         0 ($_[0], $rem) = $_[0]->copy->bdiv($d); # quot,rem in array context
53 0 0 0     0 if (! ref $d || $d < 1_000_000) {
54 0         0 return $rem->numify; # plain remainder if fits
55             }
56             } else {
57 0         0 $rem = $_[0] % $d;
58 0         0 $_[0] = int(($_[0]-$rem)/$d); # exact division stays in UV
59             }
60 0         0 return $rem;
61             }
62              
63              
64 1     1   5 use constant n_start => 1;
  1         1  
  1         55  
65 1     1   3 use constant class_x_negative => 0;
  1         2  
  1         41  
66 1     1   5 use constant class_y_negative => 0;
  1         1  
  1         65  
67 1     1   5 use constant tree_num_children_list => (0,1,2);
  1         2  
  1         1320  
68              
69             sub n_to_xy {
70 0     0 1 0 my ($self, $n) = @_;
71             ### HTree n_to_xy(): $n
72              
73 0 0       0 if ($n < 1) { return; }
  0         0  
74 0 0       0 if (is_infinite($n)) { return ($n,$n); }
  0         0  
75             {
76 0         0 my $int = int($n);
  0         0  
77             ### $int
78             ### $n
79 0 0       0 if ($n != $int) {
80 0         0 my ($x1,$y1) = $self->n_to_xy($int);
81 0         0 my ($x2,$y2) = $self->n_to_xy($int+1);
82 0         0 my $frac = $n - $int; # inherit possible BigFloat
83 0         0 my $dx = $x2-$x1;
84 0         0 my $dy = $y2-$y1;
85 0         0 return ($frac*$dx + $x1, $frac*$dy + $y1);
86             }
87 0         0 $n = $int; # BigFloat int() gives BigInt, use that
88             }
89              
90 0         0 my $zero = $n * 0;
91 0         0 my @nbits = bit_split_lowtohigh($n);
92 0         0 my $len = (2 + $zero) ** int($#nbits/2); # half power
93             ### $len
94              
95             # eg. N=9 "up" block, nbits even, $#nbits odd, dX=1 dY=0 East
96             # High 1-bit of sub-tree position does rotate +90 to North for
97             # initial step North N=8 to N=9.
98             #
99             # eg. N=17 "right" block, nbits odd, $#nbits even, dX=0 dY=-1 South
100             # High 1-bit of sub-tree position does rotate +90 to East for
101             # initial step East N=16 to N=17.
102             #
103 0         0 my $dx = ($#nbits % 2);
104 0         0 my $dy = $dx - 1;
105             ### initial direction: "$dx, $dy"
106              
107 0         0 my $x = my $y = $len;
108 0 0       0 if ($dx) {
109 0         0 $y *= 2;
110             }
111 0         0 $x -= 1;
112 0         0 $y -= 1;
113             ### initial xy: "$x, $y"
114              
115             ### assert: $nbits[-1] == 1
116 0         0 pop @nbits; # strip high 1-bit which is N=2^k spine bit
117             ### strip high spine 1-bit to: @nbits
118             # N=10001xxx
119             # ^^^^^^^ leaving these
120              
121             # Strip high 0-bits of sub-tree.
122             # Could have @nbits all 0-bits if N=2^k spine point.
123 0   0     0 while (@nbits && ! $nbits[-1]) {
124 0         0 pop @nbits;
125             }
126             ### strip high zeros to: @nbits
127             # N=10001xxx
128             # ^^^^ leaving these
129             # or if an N=1000000 spine point then @nbits now empty and no move $x,$y.
130              
131 0         0 foreach my $bit (reverse @nbits) { # high to low
132             ### at: "$x,$y bit=$bit len=$len dir=$dx,$dy"
133              
134 0         0 ($dx,$dy) = ($dy,-$dx); # rotate -90 for $bit==0
135 0 0       0 if ($bit) {
136 0         0 $dx = -$dx; # rotate 180 to give rotate +90 for $bit==1
137 0         0 $dy = -$dy;
138             }
139             ### turn to: "dir=$dx,$dy"
140              
141 0 0       0 if ($dx) { $len /= 2; } # halve when going horizontal
  0         0  
142 0         0 $x += $dx * $len;
143 0         0 $y += $dy * $len;
144             }
145              
146             ### return: "$x,$y"
147 0         0 return ($x,$y);
148             }
149              
150             # | [37] | 34=10,0010
151             # 13 | 4,13--5,13--6,13 35=10,0011
152             # | | | |
153             # 12 | | 36=10,0100
154             # | [33] | 37=10,0101
155             # 11 [35]1,7---------3,7----------5,7[34]
156             # | | |
157             # 10 0,10 | | 4,6 |
158             # | | | | | | |
159             # 9 0,9-----1,9---2,9 | 4,5---5,5---6,5
160             # | | | | [36] |
161             # 8 0,8 | 4,4
162             # |
163             # 7 [32]3,7-------------------------------
164             # |
165             # 6 0,6 2,6 | [30] [29] 17=1,0001
166             # | [9] | | | [19] | 18=1,0010
167             # 5 0,5------1,5---2,5 | [23]---5,5---[22] 20=1,0100
168             # | | | | | | | 24=1,1000
169             # 4 0,4 | 2,4 | [31] | [28]
170             # [15] | | |
171             # 3 [8]1,3---------3,3-----------5,3[17]
172             # | [16] |
173             # 2 0,2[3] | 2,2[7] [24] | [27]
174             # | | | | | |
175             # 1 0,1[2]---1,1---2,1[5] [20]4,1---5,1---6,1[21] 21=1,0101
176             # | 4 | | [18] |
177             # 0 0,0[1] 2,0[6] [25] [26]
178             #
179             # 0 1 2 3 4 5 6
180              
181             sub xy_to_n {
182 0     0 1 0 my ($self, $x, $y) = @_;
183             ### HTree xy_to_n(): "$x,$y"
184              
185 0         0 $x = round_nearest ($x);
186 0         0 $y = round_nearest ($y);
187 0 0       0 if (is_infinite($x)) {
188 0         0 return $x;
189             }
190 0 0       0 if (is_infinite($y)) {
191 0         0 return $y;
192             }
193              
194 0         0 my ($len,$exp);
195 0         0 my $n;
196 0         0 my ($xlen,$xexp) = round_down_pow($x+1, 2);
197 0         0 my ($ylen,$yexp) = round_down_pow($y+1, 2);
198 0 0       0 if ($yexp > $xexp) {
199             ### Y bigger ...
200 0         0 $len = $ylen/2;
201 0         0 $exp = $yexp;
202 0         0 $n = $len*$len*2;
203              
204 0         0 $y -= $ylen;
205             ### to: "$x,$y len=$len"
206 0 0 0     0 if ($x == $len-1 && $y == -1) {
207             ### spine ...
208 0         0 return $n;
209             }
210              
211             } else {
212             ### X bigger, fake initial X bit ...
213 0         0 $n = $xlen;
214 0         0 $len = $xlen;
215 0         0 $exp = $xexp;
216 0         0 $n = $len*$len;
217              
218 0 0 0     0 if ($x == $len-1 && $y == $len-1) {
219             ### spine ...
220 0         0 return $n;
221             }
222             }
223              
224 0 0 0     0 if ($x < 0 || $y < 0) {
225 0         0 return undef;
226             }
227              
228             ### $n
229 0         0 my @nbits; # high to low
230              
231              
232 0         0 while ($exp-- >= 0) {
233             ### X at: "$x,$y len=$len"
234             ### assert: $len >= 1
235             ### assert: $x >= 0
236             ### assert: $y >= 0
237             ### assert: $x <= 2*$len
238             ### assert: $y <= 2*$len
239              
240 0 0 0     0 if ($x == $len-1 && $y == $len-1) {
241             ### midpoint X ...
242              
243             # ### nbits HtoL: join('',@nbits)
244             # ### shift off high nbits ...
245             # shift @nbits;
246              
247 0         0 last;
248             }
249 0 0       0 if ($x >= $len) {
250             ### move left, digit 0 ...
251 0         0 $x -= $len;
252 0         0 push @nbits, 0;
253             } else {
254             ### rotate 180, digit 0: "$x,$y"
255 0         0 push @nbits, 1;
256 0         0 $x = $len-2 - $x;
257 0         0 $y = 2*$len-2 - $y;
258 0 0 0     0 if ($x < 0 || $y < 0) {
259             ### outside: "$x,$y"
260 0         0 return undef;
261             }
262             }
263              
264             ### Y at: "$x,$y len=$len"
265             ### assert: $x >= 0
266             ### assert: $y >= 0
267             ### assert: $x <= $len
268             ### assert: $y <= 2*$len
269              
270 0 0 0     0 if ($y == $len-1 && $x == $len/2-1) {
271             ### midpoint Y ...
272              
273 0         0 last;
274             }
275 0 0       0 if ($y >= $len) {
276             ### move down only, digit 1 ...
277 0         0 $y -= $len;
278 0         0 push @nbits, 1;
279             } else {
280             ### rotate 180, digit 0 ...
281 0         0 push @nbits, 0;
282 0         0 $x = $len-2 - $x;
283 0         0 $y = $len-2 - $y;
284 0 0 0     0 if ($x < 0 || $y < 0) {
285             ### outside: "$x,$y"
286 0         0 return undef;
287             }
288             }
289              
290 0         0 $len /= 2;
291             }
292              
293 0 0       0 if ($yexp > $xexp) {
294             } else {
295             ### nbits HtoL: join('',@nbits)
296             ### shift off high nbits ...
297 0         0 shift @nbits;
298             }
299              
300             ### nbits HtoL: join('',@nbits)
301              
302 0 0       0 if ($yexp > $xexp) {
303             } else {
304             }
305              
306              
307 0         0 @nbits = reverse @nbits;
308 0         0 push @nbits, 1;
309              
310             ### nbits HtoL: join('',reverse @nbits)
311 0         0 return $n + digit_join_lowtohigh(\@nbits,2);
312             }
313              
314              
315             # 7
316             # |
317             # 6 | | | | |
318             # 5 *---*---* | *---*---*
319             # 4 | | | | | | |
320             # | | |
321             # 3 *------*------*
322             # | |
323             # 2 | | | | | |
324             # 1 *---*---* *---*---*
325             # 0 | | | |
326             # 0 1 2 3 4 5 6
327             #
328             # not exact
329             sub rect_to_n_range {
330 0     0 1 0 my ($self, $x1,$y1, $x2,$y2) = @_;
331             ### HTree rect_to_n_range(): "$x1,$y1 $x2,$y2"
332              
333 0         0 $x1 = round_nearest($x1);
334 0         0 $x2 = round_nearest($x2);
335 0         0 $y1 = round_nearest($y1);
336 0         0 $y2 = round_nearest($y2);
337              
338 0         0 $x2 = max($x1,$x2);
339 0         0 $y2 = max($y1,$y2);
340 0 0 0     0 if ($x2 < 0 || $y2 < 0) {
341             ### all outside first quadrant ...
342 0         0 return (1,0);
343             }
344              
345 0         0 my ($pow) = round_down_pow(max(2*$x2, $y2) + 1,
346             2);
347 0         0 return (1, $pow*$pow);
348              
349             # my ($xpow) = round_down_pow($x2+1, 2);
350             # my ($ypow) = round_down_pow($y2+1, 2);
351             # if ($xpow > $ypow) {
352             # return (1, 2*$xpow*$xpow);
353             # } else {
354             # return (1, $ypow*$ypow);
355             # }
356             }
357              
358             # 5*2^n, 6*2^n successively
359             # being start of last two rows of each sub-tree
360             # depth=13 160 10100000
361             # depth=14 192 11000000
362             #
363             sub tree_depth_to_n {
364 0     0 1 0 my ($self, $depth) = @_;
365             ### HTree depth_to_n(): $depth
366 0         0 $depth = int($depth);
367 0 0       0 if ($depth < 3) {
368 0 0       0 if ($depth < 0) { return undef; }
  0         0  
369 0         0 return $depth + 1;
370             }
371 0         0 ($depth, my $rem) = _divrem ($depth-3, 2);
372 0         0 return (5+$rem) * 2**$depth;
373             }
374              
375             # spine 2^n
376             #
377             sub tree_depth_to_n_end {
378 0     0 1 0 my ($self, $depth) = @_;
379             ### HTree depth_to_n(): $depth
380 0 0       0 if ($depth < 0) {
381 0         0 return undef;
382             }
383 0         0 return 2**int($depth);
384             }
385              
386             # 0 N=1
387             # |
388             # 1 N=2--
389             # | \
390             # 2 N=3 N=4-------
391             # | \
392             # 3 N=5 N=8 ------------
393             # / \ | \
394             # 4 N=6 N=7 N=9 N=16----------
395             # / \ | \
396             # 5 N=10 N=11 N=17 N=32------
397             # / \ / \ / \ | \
398             # 6 N=12 N=13 N=14 N=15 N=18 N=19 N=33 N=64---
399             # / \ / \ | \
400             # N=20 [4of] N=34 N=35 N=65 N=128
401             # 0 1 = 1
402             # 1 1 = 1
403             # 2 2 = 1 + 1
404             # 3 2 = 1 + 1
405             # 4 4 = 2 + 1 + 1
406             # 5 4 = 2 + 1 + 1
407             # 6 8 = 4 + 2 + 1 + 1
408             # 7 8
409             # 8 16
410             # 9 16
411             #
412             # 1 + sum i=0 to floor(depth/2)-1 of 2^i
413             # = 2^floor(depth/2)
414              
415             sub tree_depth_to_width {
416 0     0 1 0 my ($self, $depth) = @_;
417             ### HTree tree_n_to_subheight(): $depth
418              
419 0 0       0 if ($depth < 0) {
420 0         0 return undef;
421             }
422 0         0 $depth = int($depth/2);
423 0         0 return 2**$depth;
424             }
425              
426              
427             sub tree_n_to_depth {
428 0     0 1 0 my ($self, $n) = @_;
429             ### HTree n_to_depth(): $n
430              
431 0 0       0 if ($n < 1) { return undef; }
  0         0  
432 0         0 $n = int($n);
433 0 0       0 if (is_infinite($n)) { return $n; }
  0         0  
434 0         0 my ($pow,$depth) = round_down_pow($n,2);
435 0 0       0 if ($n -= $pow) {
436 0         0 ($pow, my $exp) = round_down_pow($n,2);
437 0         0 $depth += $exp+1;
438             }
439 0         0 return $depth;
440             }
441              
442              
443             # (n-pow)*2 + pow
444             # = 2*n-2*pow+pow
445             # = 2*n-pow
446             # (n-pow)*2 < pow
447             # 2*n-2*pow < pow
448             # 2*n-pow < 2*pow
449             # 1011 -> 011 -> 110 -> 1110
450             sub tree_n_children {
451 0     0 1 0 my ($self, $n) = @_;
452             ### HTree tree_n_children(): $n
453              
454 0 0       0 if ($n < 1) {
455 0         0 return;
456             }
457              
458 0         0 my ($pow) = round_down_pow($n,2);
459 0 0       0 if ($pow == 1) {
460 0         0 return $n+1;
461             }
462 0 0       0 if ($n == $pow) {
463 0         0 return ($n+1, 2*$n);
464             }
465              
466 0         0 $n *= 2;
467 0         0 $n -= $pow;
468 0 0       0 if ($n < 2*$pow) {
469 0         0 return ($n, $n+1);
470             } else {
471 0         0 return;
472             }
473             }
474              
475             # 1
476             # 2 3
477             # 4 5, 6,7
478             # 101 11x
479             # 8 9, 10,11, 12,13,14,15
480             # 1001 1010,1011 1100,1101
481             #
482             # (n-pow)/2 + pow
483             # = (n-pow+2*pow)/2
484             # = (n+pow)/2
485             #
486             sub tree_n_parent {
487 0     0 1 0 my ($self, $n) = @_;
488             ### HTree tree_n_parent(): $n
489              
490 0 0       0 if ($n < 2) {
491 0         0 return undef;
492             }
493 0         0 my ($pow) = round_down_pow($n,2);
494 0 0       0 if ($n == $pow) {
495 0         0 return $n/2;
496             }
497 0         0 return ($n + $pow - ($n%2)) / 2;
498             }
499              
500             # length of the run of 0s immediately below the high 1-bit
501             # 10001111
502             # ^^^------ 3 0-bits
503             #
504             sub tree_n_to_subheight {
505 0     0 1 0 my ($self, $n) = @_;
506             ### HTree tree_n_to_subheight(): $n
507              
508 0 0       0 if ($n < 2) {
509 0         0 return undef;
510             }
511 0         0 my ($pow,$exp) = round_down_pow($n,2);
512 0 0       0 if ($n == $pow) {
513 0         0 return undef; # spine, infinite
514             }
515 0         0 $n -= $pow;
516 0         0 ($pow, my $exp2) = round_down_pow($n,2);
517 0         0 return $exp - $exp2 - 1;
518             }
519              
520              
521             #------------------------------------------------------------------------------
522             # levels
523              
524             sub level_to_n_range {
525 4     4 1 435 my ($self, $level) = @_;
526 4         15 return (1, 2**(2*$level+1) - 1);
527             }
528             sub n_to_level {
529 0     0 1   my ($self, $n) = @_;
530 0 0         if ($n < 1) { return undef; }
  0            
531 0 0         if (is_infinite($n)) { return $n; }
  0            
532 0           $n = round_nearest($n);
533 0           _divrem_mutate ($n, 2);
534 0           my ($pow, $exp) = round_down_pow ($n, 4);
535 0           return $exp + 1;
536             }
537              
538             #------------------------------------------------------------------------------
539             1;
540             __END__