File Coverage

blib/lib/Math/PlanePath/UlamWarburtonQuarter.pm
Criterion Covered Total %
statement 176 237 74.2
branch 67 112 59.8
condition 16 26 61.5
subroutine 21 28 75.0
pod 15 15 100.0
total 295 418 70.5


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Kevin Ryde
2              
3             # This file is part of Math-PlanePath.
4             #
5             # Math-PlanePath is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-PlanePath is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-PlanePath. If not, see .
17              
18              
19             package Math::PlanePath::UlamWarburtonQuarter;
20 2     2   1360 use 5.004;
  2         6  
21 2     2   10 use strict;
  2         3  
  2         41  
22 2     2   9 use Carp 'croak';
  2         4  
  2         88  
23 2     2   11 use List::Util 'sum';
  2         5  
  2         155  
24              
25 2     2   12 use vars '$VERSION', '@ISA';
  2         4  
  2         102  
26             $VERSION = 128;
27 2     2   786 use Math::PlanePath;
  2         3  
  2         105  
28             @ISA = ('Math::PlanePath');
29             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
30              
31             use Math::PlanePath::Base::Generic
32 2         92 'is_infinite',
33 2     2   11 'round_nearest';
  2         4  
34             use Math::PlanePath::Base::Digits
35 2         168 'round_down_pow',
36             'bit_split_lowtohigh',
37             'digit_split_lowtohigh',
38 2     2   543 'digit_join_lowtohigh';
  2         5  
39              
40             # uncomment this to run the ### lines
41             # use Smart::Comments;
42              
43              
44 2         121 use constant parameter_info_array =>
45             [
46             { name => 'parts',
47             share_key => 'parts_ulamwarburton_quarter',
48             display => 'Parts',
49             type => 'enum',
50             default => '1',
51             choices => ['1','octant','octant_up' ],
52             choices_display => ['1','Octant','Octant Up' ],
53             description => 'Which parts of the plane to fill.',
54             },
55             Math::PlanePath::Base::Generic::parameter_info_nstart1(),
56 2     2   13 ];
  2         4  
57              
58 2     2   14 use constant class_x_negative => 0;
  2         2  
  2         85  
59 2     2   9 use constant class_y_negative => 0;
  2         4  
  2         2097  
60              
61             sub diffxy_minimum {
62 0     0 1 0 my ($self) = @_;
63 0 0       0 return ($self->{'parts'} eq 'octant' ? 0 : undef);
64             }
65             sub diffxy_maximum {
66 0     0 1 0 my ($self) = @_;
67 0 0       0 return ($self->{'parts'} eq 'octant_up' ? 0 : undef);
68             }
69              
70             # Minimum dir=0 at N=13 dX=2,dY=0.
71             # Maximum dir seems dX=13,dY=-9 at N=149 going top-left part to new bottom
72             # right diagonal.
73             my %dir_maximum_dxdy = (1 => [13,-9],
74             octant => [1,-1], # South-East
75             octant_up => [0,-1], # South
76             );
77             sub dir_maximum_dxdy {
78 0     0 1 0 my ($self) = @_;
79 0         0 return @{$dir_maximum_dxdy{$self->{'parts'}}};
  0         0  
80             }
81              
82             sub tree_num_children_list {
83 0     0 1 0 my ($self) = @_;
84 0 0       0 return ($self->{'parts'} =~ /octant/
85             ? (0, 1, 2, 3)
86             : (0, 1, 3));
87             }
88              
89             #------------------------------------------------------------------------------
90             sub new {
91 12     12 1 1413 my $self = shift->SUPER::new(@_);
92 12 100       42 if (! defined $self->{'n_start'}) {
93 8         32 $self->{'n_start'} = $self->default_n_start;
94             }
95 12   50     52 my $parts = ($self->{'parts'} ||= '1');
96 12 50       27 if (! exists $dir_maximum_dxdy{$parts}) {
97 0         0 croak "Unrecognised parts option: ", $parts;
98             }
99 12         25 return $self;
100             }
101              
102             # 7 7 7 7
103             # 6 6
104             # 7 5 5 7
105             # 4
106             # 3 3 5 7
107             # 2 6
108             # 1 3 7 7
109             #
110             # 1+1+3=5
111             # 5+1+3*5=21
112             # 1+3 = 4
113             # 1+3+3+9 = 16
114             #
115             # 0
116             # 1 0 +1
117             # 2 1 +1 <- 1
118             # 3 2 +3
119             # 4 5 +1 <- 1 + 4 = 5
120             # 5 6 +3
121             # 6 9 +3
122             # 7 12 +9
123             # 8 21 <- 1 + 4 + 16 = 21
124              
125             # 1+3 = 4 power 2
126             # 1+3+3+9 = 16 power 3
127             # 1+3+3+9+3+9+9+27 = 64 power 4
128             #
129             # (1+4+16+...+4^(l-1)) = (4^l-1)/3
130             # l=1 total=(4-1)/3 = 1
131             # l=2 total=(16-1)/3 = 5
132             # l=3 total=(64-1)/3=63/3 = 21
133             #
134             # n = 1 + (4^l-1)/3
135             # n-1 = (4^l-1)/3
136             # 3n-3 = (4^l-1)
137             # 3n-2 = 4^l
138             #
139             # 3^0+3^1+3^1+3^2 = 1+3+3+9=16
140             # x+3x+3x+9x = 16x = 256
141             #
142             # 22
143             # 20 19 18 17
144             # 12 11
145             # 21 9 8 16
146             # 6
147             # 5 4 7 15
148             # 2 10
149             # 1 3 13 14
150             #
151              
152             sub n_to_xy {
153 117     117 1 6545 my ($self, $n) = @_;
154             ### UlamWarburtonQuarter n_to_xy(): $n
155              
156 117 50       274 if ($n < $self->{'n_start'}) { return; }
  0         0  
157 117 50       264 if (is_infinite($n)) { return ($n,$n); }
  0         0  
158              
159             {
160 117         201 my $int = int($n);
  117         188  
161             ### $int
162             ### $n
163 117 50       229 if ($n != $int) {
164 0         0 my ($x1,$y1) = $self->n_to_xy($int);
165 0         0 my ($x2,$y2) = $self->n_to_xy($int+1);
166 0         0 my $frac = $n - $int; # inherit possible BigFloat
167 0         0 my $dx = $x2-$x1;
168 0         0 my $dy = $y2-$y1;
169 0         0 return ($frac*$dx + $x1, $frac*$dy + $y1);
170             }
171 117         184 $n = $int; # BigFloat int() gives BigInt, use that
172             }
173              
174 117         215 $n = $n - $self->{'n_start'} + 1; # N=1 basis
175 117 100       222 if ($n == 1) { return (0,0); }
  8         22  
176              
177 109 50       233 my ($depthsum, $nrem, $rowwidth) = _n1_to_depthsum_rem_width($self,$n)
178             or return ($n,$n); # N==nan or N==+inf
179              
180             ### assert: $nrem >= 0
181             ### assert: $nrem < $width
182 109 50       266 if ($self->{'parts'} eq 'octant_up') {
183 0         0 $nrem += ($rowwidth-1)/2;
184             ### assert: $nrem < $width
185             }
186              
187 109         265 my @ndigits = digit_split_lowtohigh($nrem,3);
188 109         236 my $dhigh = shift(@$depthsum) - 1; # highest term
189 109         166 my $x = 0;
190 109         143 my $y = 0;
191 109         223 foreach my $depthsum (reverse @$depthsum) { # depth terms low to high
192 234         350 my $ndigit = shift @ndigits; # N digits low to high
193             ### $depthsum
194             ### $ndigit
195              
196 234         346 $x += $depthsum;
197 234         315 $y += $depthsum;
198             ### depthsum to xy: "$x,$y"
199              
200 234 100       365 if ($ndigit) {
201 149 100       269 if ($ndigit == 2) {
202 77         172 ($x,$y) = (-$y,$x); # rotate +90
203             }
204             } else {
205             # digit==0 (or undef when run out of @ndigits)
206 85         164 ($x,$y) = ($y,-$x); # rotate -90
207             }
208             ### rotate to: "$x,$y"
209             }
210              
211             ### final: "$x,$y"
212 109         297 return ($dhigh + $x, $dhigh + $y);
213             }
214              
215             sub xy_to_n {
216 560     560 1 8091 my ($self, $x, $y) = @_;
217             ### UlamWarburtonQuarter xy_to_n(): "$x, $y"
218              
219 560         1133 $x = round_nearest ($x);
220 560         1117 $y = round_nearest ($y);
221 560         982 my $parts = $self->{'parts'};
222 560 50 100     1793 if ($y < 0
    100 33        
      66        
223             || $x < ($parts eq 'octant' ? $y : 0)
224             || ($parts eq 'octant_up' && $x > $y)) {
225 329         603 return undef;
226             }
227 231 100 100     495 if ($x == 0 && $y == 0) {
228 9         23 return $self->{'n_start'};
229             }
230 222         317 $x += 1; # pushed away by 1 ...
231 222         293 $y += 1;
232              
233 222         509 my ($len, $exp) = round_down_pow ($x + $y, 2);
234 222 50       489 if (is_infinite($exp)) { return $exp; }
  0         0  
235              
236 222         435 my $depth
237             = my $n
238             = ($x * 0 * $y); # inherit bignum 0
239 222         334 my $rowwidth = $depth + 1;
240              
241 222         418 while ($exp-- >= 0) {
242             ### at: "$x,$y n=$n len=$len"
243              
244             # first quadrant square
245             ### assert: $x >= 0
246             ### assert: $y >= 0
247             # ### assert: $x < 2*$len
248             # ### assert: $y < 2*$len
249              
250 831 100 100     1992 if ($x >= $len || $y >= $len) {
251             # one of three quarters away from origin
252             # +---+---+
253             # | 2 | 1 |
254             # +---+---+
255             # | | 0 |
256             # +---+---+
257              
258 519         739 $x -= $len;
259 519         670 $y -= $len;
260             ### shift to: "$x,$y"
261              
262 519 100       798 if ($x) {
263 343 100       594 unless ($y) {
264 46         119 return undef; # x==0, y!=0, nothing
265             }
266             } else {
267 176 100       314 if ($y) {
268 45         119 return undef; # x!=0, y-=0, nothing
269             }
270             }
271              
272 428         588 $depth += $len;
273 428 100 66     961 if ($x || $y) {
274 297         413 $rowwidth *= 3;
275 297         374 $n *= 3;
276 297 100       529 if ($y < 0) {
    100          
277             ### bottom right, digit 0 ...
278 105         237 ($x,$y) = (-$y,$x); # rotate +90
279             } elsif ($x >= 0) {
280             ### top right, digit 1 ...
281 88         155 $n += 1;
282             } else {
283             ### top left, digit 2 ...
284 104         175 ($x,$y) = ($y,-$x); # rotate -90
285 104         161 $n += 2;
286             }
287             }
288             }
289              
290 740         1354 $len /= 2;
291             }
292              
293             ### $n
294             ### $depth
295              
296 131 50       269 if ($self->{'parts'} eq 'octant_up') {
297 0         0 $n -= ($rowwidth-1)/2;
298             }
299              
300 131         317 return $n + $self->tree_depth_to_n($depth-1);
301             }
302              
303             # not exact
304             sub rect_to_n_range {
305 50     50 1 4060 my ($self, $x1,$y1, $x2,$y2) = @_;
306             ### UlamWarburtonQuarter rect_to_n_range(): "$x1,$y1 $x2,$y2"
307              
308 50         162 $x1 = round_nearest ($x1);
309 50         111 $y1 = round_nearest ($y1);
310 50         104 $x2 = round_nearest ($x2);
311 50         103 $y2 = round_nearest ($y2);
312              
313 50 50       114 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
314 50 50       99 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
315              
316 50 50 33     180 if ($x2 < 0 || $y2 < 0) {
317 0         0 return (1, 0); # all outside first quadrant
318             }
319              
320 50 50       124 if ($x1 < 0) { $x1 *= 0; }
  0         0  
321 50 50       81 if ($y1 < 0) { $y1 *= 0; }
  0         0  
322              
323             # level numbers
324 50 100       102 my $dlo = ($x1 > $y1 ? $x1 : $y1)+1;
325 50 100       100 my $dhi = ($x2 > $y2 ? $x2 : $y2);
326             ### $dlo
327             ### $dhi
328              
329             # round down to level=2^k numbers
330 50 50       98 if ($dlo) {
331 50         148 ($dlo) = round_down_pow ($dlo,2);
332             }
333 50         114 ($dhi) = round_down_pow ($dhi,2);
334              
335             ### rounded to pow2: "$dlo ".(2*$dhi)
336              
337 50         138 return ($self->tree_depth_to_n($dlo-1),
338             $self->tree_depth_to_n(2*$dhi-1));
339             }
340              
341             #------------------------------------------------------------------------------
342 2     2   15 use constant tree_num_roots => 1;
  2         3  
  2         2002  
343              
344             # ENHANCE-ME: step by the bits, not by X,Y
345             sub tree_n_children {
346 9     9 1 728 my ($self, $n) = @_;
347 9 50       27 if ($n < $self->{'n_start'}) {
348 0         0 return;
349             }
350 9         24 my ($x,$y) = $self->n_to_xy($n);
351 9         13 my @ret;
352 9         14 my $dx = 1;
353 9         13 my $dy = 1;
354 9         20 foreach (1 .. 4) {
355 36 100       78 if (defined (my $n_child = $self->xy_to_n($x+$dx,$y+$dy))) {
356 19 100       36 if ($n_child > $n) {
357 11         19 push @ret, $n_child;
358             }
359             }
360 36         460 ($dx,$dy) = (-$dy,$dx); # rotate +90
361             }
362 9         38 return sort {$a<=>$b} @ret;
  4         14  
363             }
364             sub tree_n_parent {
365 12     12 1 902 my ($self, $n) = @_;
366 12 100       31 if ($n <= $self->{'n_start'}) {
367 1         2 return undef;
368             }
369 11         30 my ($x,$y) = $self->n_to_xy($n);
370 11         17 my $dx = 1;
371 11         17 my $dy = 1;
372 11         22 foreach (1 .. 4) {
373 33 100       75 if (defined (my $n_parent = $self->xy_to_n($x+$dx,$y+$dy))) {
374 24 100       52 if ($n_parent < $n) {
375 11         27 return $n_parent;
376             }
377             }
378 22         57 ($dx,$dy) = (-$dy,$dx); # rotate +90
379             }
380 0         0 return undef;
381             }
382              
383             # level = depth+1 = 2^a + 2^b + 2^c + 2^d ... a>b>c>d...
384             # Ndepth = 1 + (-1
385             # + 4^a
386             # + 3 * 4^b
387             # + 3^2 * 4^c
388             # + 3^3 * 4^d + ...) / 3
389             sub tree_depth_to_n {
390 898     898 1 1845 my ($self, $depth) = @_;
391             ### tree_depth_to_n(): $depth
392 898 50       1708 if (is_infinite($depth)) {
393 0         0 return $depth;
394             }
395 898 50       2145 unless ($depth >= 0) {
396 0         0 return undef;
397             }
398 898         1223 my $n = $depth*0; # inherit bignum 0
399 898         1234 my $pow3 = 1 + $n; # inherit bignum 1
400 898         1924 foreach my $bit (reverse bit_split_lowtohigh($depth+1)) { # high to low
401 3766         4572 $n *= 4;
402 3766 100       5771 if ($bit) {
403 1956         2246 $n += $pow3;
404 1956         2536 $pow3 *= 3;
405             }
406             }
407 898 50       2055 if ($self->{'parts'} =~ /octant/) {
408 0         0 $n = ($n + (3*$depth-1))/6;
409             } else {
410 898         1363 $n = ($n-1)/3;
411             }
412 898         2071 return $n + $self->{'n_start'};
413             }
414              
415             sub tree_n_to_depth {
416 0     0 1 0 my ($self, $n) = @_;
417              
418 0         0 $n = int($n - $self->{'n_start'} + 1); # N=1 basis
419 0 0       0 if ($n < 1) {
420 0         0 return undef;
421             }
422 0 0       0 (my $depthsum, $n) = _n1_to_depthsum_rem_width($self,$n)
423             or return $n; # N==nan or N==+infinity
424 0         0 return sum(-1, @$depthsum);
425             }
426              
427             # Return ($aref, $remaining_n).
428             # sum(@$aref) = depth starting depth=1
429             #
430             # depth+1 = 2^k
431             # Ndepth(depth) = (4^k+2)/3
432             # 3N-2 = 4^k
433             # NdepthOct(depth) = ((4^k+2)/3 + 2^k)/2
434             # 6N-2 = 4^k + 3*2^k
435             #
436             sub _n1_to_depthsum_rem_width {
437 109     109   218 my ($self, $n) = @_;
438             ### _n1_to_depthsum_rem_width(): $n
439              
440 109         190 my $octant = ($self->{'parts'} =~ /octant/);
441 109 50       322 my ($power, $exp) = round_down_pow (($octant ? 6 : 3)*$n - 2, 4);
442 109 50       277 if (is_infinite($exp)) {
443 0         0 return;
444             }
445              
446             ### $power
447             ### $exp
448             ### pow base: ($power - 1)/3 + 1
449              
450             {
451 109         194 my $sub = ($power + 2)/3; # (power-1)/3 + 1
  109         190  
452 109 50       210 if ($octant) {
453 0         0 $sub = ($sub + 2**$exp) / 2;
454             ### prospective sub: $sub
455             ### assert: $sub == ($power + 3 * 2 ** $exp + 2)/6
456              
457 0 0       0 if ($sub > $n) {
458 0         0 $exp -= 1;
459 0         0 $power /= 4;
460 0         0 $sub = ($power + 3*2**$exp + 2)/6;
461             }
462             }
463             ### assert: $sub <= $n
464 109         188 $n -= $sub;
465             }
466             ### n less pow base: $n
467              
468 109         242 my @depthsum = (2**$exp);
469              
470             # find the cumulative levelpoints total <= $n, being the start of the
471             # level containing $n
472             #
473 109         167 my $factor = 1;
474 109         214 while (--$exp >= 0) {
475 368         504 $power /= 4;
476 368         540 my $sub = $power * $factor;
477 368 50       610 if ($octant) {
478 0         0 $sub = ($sub + 2**$exp)/2;
479             }
480             ### $sub
481 368         497 my $rem = $n - $sub;
482              
483             ### $n
484             ### $power
485             ### $factor
486             ### consider subtract: $sub
487             ### $rem
488              
489 368 100       732 if ($rem >= 0) {
490 234         308 $n = $rem;
491 234         374 push @depthsum, 2**$exp;
492 234         422 $factor *= 3;
493             }
494             }
495              
496             ### _n1_to_depthsum_rem_width() result ...
497             ### @depthsum
498             ### remaining n: $n
499             ### assert: $n >= 0
500             ### assert: $n < $factor
501              
502 109         405 return (\@depthsum, $n, $factor);
503             }
504              
505              
506             # at 0,2 turn and new height limit
507             # at 1 keep existing depth limit
508             # N=30 rem=1 = 0,1 depth=11=8+2+1=1011 width=9
509             #
510             sub tree_n_to_subheight {
511 0     0 1 0 my ($self, $n) = @_;
512             ### tree_n_to_subheight(): $n
513              
514 0         0 $n = int($n - $self->{'n_start'} + 1); # N=1 basis
515 0 0       0 if ($n < 1) {
516 0         0 return undef;
517             }
518 0 0       0 my ($depthsum, $nrem, $rowwidth) = _n1_to_depthsum_rem_width($self,$n)
519             or return $n; # N==nan or N==+infinity
520             ### $depthsum
521             ### $nrem
522              
523 0 0       0 if ($self->{'parts'} eq 'octant_up') {
524 0         0 $nrem += ($rowwidth-1)/2;
525             }
526              
527 0         0 my $sub = pop @$depthsum;
528 0   0     0 while (@$depthsum && _divrem_mutate($nrem,3) == 1) {
529 0         0 $sub += pop @$depthsum;
530             }
531 0 0       0 if (@$depthsum) {
532 0         0 return $depthsum->[-1] - 1 - $sub;
533             } else {
534 0         0 return undef; # $nrem all 1-digits
535             }
536             }
537              
538             #------------------------------------------------------------------------------
539             # levels
540              
541             sub level_to_n_range {
542 27     27 1 1844 my ($self, $level) = @_;
543 27         107 return ($self->{'n_start'},
544             $self->tree_depth_to_n_end(2**($level+1) - 2));
545             }
546             sub n_to_level {
547 0     0 1   my ($self, $n) = @_;
548 0           my $depth = $self->tree_n_to_depth($n);
549 0 0         if (! defined $depth) { return undef; }
  0            
550 0           my ($pow, $exp) = round_down_pow ($depth+1, 2);
551 0           return $exp;
552             }
553              
554             #------------------------------------------------------------------------------
555             1;
556             __END__