File Coverage

blib/lib/Math/PlanePath/ToothpickReplicate.pm
Criterion Covered Total %
statement 110 225 48.8
branch 31 110 28.1
condition 1 15 6.6
subroutine 15 20 75.0
pod 6 6 100.0
total 163 376 43.3


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             # block_order => 'AB123'
19             # block_order => 'A1B32' is depth first and finite parts first,
20             # in parts=1 where single infinite spine
21             #
22             # maybe tree methods same structure as ToothpickTree
23             #
24              
25             # cf A175262 odd binary length and middle digit 1
26             # A175263 odd binary length and middle digit 0
27             #
28              
29             package Math::PlanePath::ToothpickReplicate;
30 1     1   1315 use 5.004;
  1         2  
  1         30  
31 1     1   4 use strict;
  1         1  
  1         47  
32             #use List::Util 'max';
33             *max = \&Math::PlanePath::_max;
34              
35 1     1   4 use vars '$VERSION', '@ISA';
  1         2  
  1         63  
36             $VERSION = 16;
37 1     1   638 use Math::PlanePath;
  1         4687  
  1         120  
38             @ISA = ('Math::PlanePath');
39              
40              
41             # return ($quotient, $remainder)
42             sub _divrem {
43 14     14   13 my ($n, $d) = @_;
44 14 50 33     27 if (ref $n && $n->isa('Math::BigInt')) {
45 0         0 my ($quot,$rem) = $n->copy->bdiv($d);
46 0 0 0     0 if (! ref $d || $d < 1_000_000) {
47 0         0 $rem = $rem->numify; # plain remainder if fits
48             }
49 0         0 return ($quot, $rem);
50             }
51 14         13 my $rem = $n % $d;
52 14         21 return (int(($n-$rem)/$d), # exact division stays in UV
53             $rem);
54             }
55              
56              
57             use Math::PlanePath::Base::Generic
58 1         37 'is_infinite',
59 1     1   5 'round_nearest';
  1         2  
60             use Math::PlanePath::Base::Digits
61 1     1   462 'round_down_pow';
  1         1149  
  1         51  
62              
63             # uncomment this to run the ### lines
64             # use Smart::Comments;
65              
66 1     1   740 use Math::PlanePath::ToothpickTree;
  1         2  
  1         119  
67             *new = \&Math::PlanePath::ToothpickTree::new;
68             *x_negative = \&Math::PlanePath::ToothpickTree::x_negative;
69             *y_negative = \&Math::PlanePath::ToothpickTree::y_negative;
70             *rect_to_n_range = \&Math::PlanePath::ToothpickTree::rect_to_n_range;
71             *x_minimum = \&Math::PlanePath::ToothpickTree::x_minimum;
72             *y_minimum = \&Math::PlanePath::ToothpickTree::y_minimum;
73             *sumxy_minimum = \&Math::PlanePath::ToothpickTree::sumxy_minimum;
74             *sumabsxy_minimum = \&Math::PlanePath::ToothpickTree::sumabsxy_minimum;
75             *rsquared_minimum = \&Math::PlanePath::ToothpickTree::rsquared_minimum;
76              
77 1         94 use constant parameter_info_array =>
78             [ { name => 'parts',
79             share_key => 'parts_toothpickreplicate',
80             display => 'Parts',
81             type => 'enum',
82             default => '4',
83             choices => ['4','3','2','1'],
84             choices_display => ['4','3','2','1'],
85             description => 'Which parts of the pattern to generate.',
86             },
87 1     1   7 ];
  1         0  
88              
89 1     1   5 use constant n_start => 0;
  1         1  
  1         40  
90 1     1   3 use constant class_x_negative => 1;
  1         1  
  1         35  
91 1     1   3 use constant class_y_negative => 1;
  1         1  
  1         107  
92              
93             {
94             my @x_negative_at_n = (undef,
95             undef, # 1
96             3, # 2
97             6, # 3
98             5, # 4
99             );
100             sub x_negative_at_n {
101 0     0 1 0 my ($self) = @_;
102 0         0 return $x_negative_at_n[$self->{'parts'}];
103             }
104             }
105             {
106             my @y_negative_at_n = (undef,
107             undef, # 1
108             undef, # 2
109             2, # 3
110             2, # 4
111             );
112             sub y_negative_at_n {
113 0     0 1 0 my ($self) = @_;
114 0         0 return $y_negative_at_n[$self->{'parts'}];
115             }
116             }
117              
118             # parts=1 same as parts=4
119             # parts=2 same as parts=4
120             # parts=3 same as parts=4
121             # parts=4 33,-12
122             # 133,-30
123             # 333,-112
124             # 1333,-230
125             # 3332,-1112 -> 3,-1
126 1     1   4 use constant dir_maximum_dxdy => (3,-1);
  1         1  
  1         1151  
127              
128             #------------------------------------------------------------------------------
129             # Fraction covered
130             # Xlevel = 2^(level+1) - 1
131             # Ylevel = 2^(level+1)
132             # Nend = (2*4^(level+1) + 1)/3 - 1
133             #
134             # Nend / (Xlevel*Ylevel)
135             # -> ((2*4^(level+1) + 1)/3 - 1) / 4^(level+1)
136             # -> (2*4^(level+1) + 1)/3 / 4^(level+1)
137             # -> 2*4^(level+1)/3 / 4^(level+1)
138             # -> 2/3
139              
140             # Leading diagonal 1,3, 7,11,
141             # 23,25,29,43, +22,22,22,32
142             # 87,89,93,97, +86,86,86,86
143             # 109,111,115,171, +86,128
144             # 343
145             # part2start = (4^level + 5)/3 = 3,7,23,87,343
146             # sums of part2start(level), but +2 in second half of each
147             # (3)/3=1
148             # (3+ 1+5)/3=3
149             # (3+ 1+5 + 4+5)/3=9
150              
151              
152             # v v
153             # | -> | part 3
154             # +---h h---+
155             #
156             # +---v h
157             # | -> | part 1 rot then part 3
158             # h +---v
159             #
160             # v v
161             # | -> | part 3 then part 3 again
162             # h---+ +---h
163             #
164              
165             # v +---v
166             # | -> | part 1
167             # +---h h
168             #
169             # v v---+
170             # | -> | part 3 then part 1 rot is +90
171             # h---+ h
172              
173             # N = (2*4^level + 1)/3 + 1 is first of "level"
174             # 3N-3 = 2*4^level + 1
175             # 2*4^level = 3N-4
176             # 4^(level+1) = 6N-8
177             #
178             # part = (2*4^level - 2)/3 many points in "level"
179             # above = (2*4^(level+1) - 2)/3
180             # = (4*2*4^level - 2)/3
181             # = 4*(2*4^level - 2/4)/3
182             # = 4*(2*4^level - 2)/3 + 4*(+ 2 - 2/4)/3
183             # = 4*(2*4^level - 2)/3 + 2
184             # = 4*part + 2
185             # part = (above-2)/4
186              
187             my @quadrant_to_hdx = (1,-1, -1,1);
188             my @quadrant_to_vdy = (1, 1, -1,-1);
189              
190             sub n_to_xy {
191 54     54 1 2738 my ($self, $n) = @_;
192             ### ToothpickReplicate n_to_xy(): $n
193              
194 54 50       103 if ($n < 0) { return; }
  0         0  
195 54 50       85 if (is_infinite($n)) { return ($n,$n); }
  0         0  
196              
197             {
198 54         242 my $int = int($n);
  54         42  
199             ### $int
200             ### $n
201 54 50       74 if ($n != $int) {
202 0         0 my ($x1,$y1) = $self->n_to_xy($int);
203 0         0 my ($x2,$y2) = $self->n_to_xy($int+1);
204 0         0 my $frac = $n - $int; # inherit possible BigFloat
205 0         0 my $dx = $x2-$x1;
206 0         0 my $dy = $y2-$y1;
207 0         0 return ($frac*$dx + $x1, $frac*$dy + $y1);
208             }
209 54         37 $n = $int; # BigFloat int() gives BigInt, use that
210             }
211              
212 54         52 my $parts = $self->{'parts'};
213 54         41 my $x = 0;
214 54         49 my $y = 0;
215 54         31 my $hdx = 1;
216 54         37 my $hdy = 0;
217 54         33 my $vdx = 0;
218 54         38 my $vdy = 1;
219              
220 54 50       128 if ($parts eq '2') {
    100          
    100          
221 0 0       0 if ($n == 0) {
222 0         0 return (0,1);
223             }
224              
225             # first of a replication level
226             # Nlevel = 2*(2*4^level - 2)/3 + 1
227             # = (4*4^level - 4)/3 + 1
228             # = (4*4^level - 4 + 3)/3
229             # = (4*4^level - 1)/3 = 5,21
230             # 3N = 4*4^level - 1
231             # 4^(level+1) = 3N+1
232              
233 0         0 my ($len,$level) = round_down_pow(3*$n+1, 4);
234 0         0 my $three_parts = $len/2;
235              
236             ### $len
237             ### $level
238             ### $three_parts
239             ### start this level: ($len-1)/3
240             ### n reduced: $n-($len-1)/3
241              
242 0         0 (my $quadrant, $n) = _divrem ($n-($len-1)/3, $three_parts);
243             ### $quadrant
244             ### n remainder: $n
245             ### assert: $quadrant >= 0
246             ### assert: $quadrant <= 1
247              
248 0         0 $n += ($len/2-2)/3;
249 0 0       0 if ($quadrant) { $hdx = -1; }
  0         0  
250             ### n in quarter: $n
251              
252             } elsif ($parts == 3) {
253 8 100       13 if ($n <= 1) {
254 2         5 return (0,$n);
255             }
256             # Nend = 3*(2*4^level - 2)/3 + 2
257             # = (2*4^level - 2) + 2
258             # = 2*4^level = 2,8,32
259             # N-1 = 2*4^level
260             # 4^(level+1) = 2N-2
261              
262 6         34 my ($len,$level) = round_down_pow(2*$n, 4);
263 6         50 my $three_parts = $len/2;
264              
265             ### $len
266             ### $level
267             ### $three_parts
268             ### start this level: ($len/2+1)
269             ### n reduced: $n-($len/2+1)
270              
271 6         13 (my $quadrant, $n) = _divrem ($n-$len/2, $three_parts);
272             ### $quadrant
273             ### n remainder: $n
274             ### assert: $quadrant >= 0
275             ### assert: $quadrant <= 2
276              
277 6         8 $n += ($len/2-2)/3;
278             ### n in quarter: $n
279              
280 6 100       11 if ($quadrant == 0) {
    100          
281 2         2 $hdx = 0; # rotate -90
282 2         2 $hdy = -1;
283 2         1 $vdx = 1;
284 2         2 $vdy = 0;
285 2         2 $x = -1; # offset
286             } elsif ($quadrant == 2) {
287 2         2 $hdx = -1; # mirror
288             }
289              
290             } elsif ($parts == 4) {
291 11 100       16 if ($n <= 2) {
292 3 100       5 if ($n == 0) { return (0,0); }
  1         3  
293 2 100       3 if ($n == 1) { return (0,1); }
  1         3  
294 1         2 return (0,-1); # N==2
295             }
296             # first of a replication level
297             # Nlevel = 4*(2*4^level - 2)/3 + 3
298             # = (8*4^level - 8)/3 + 3
299             # = (8*4^level - 8 + 9)/3
300             # = (8*4^level+1)/3 11,43,171
301             # 3N = 8*4^level+1
302             # 8*4^level = 3N-1
303             # 4^(level+2) = 6N-2
304             #
305             # first of this level, using level+2
306             # Nlevel = (4^(level+2)/2+1)/3
307             # = (4^(level+2)+2)/6
308             #
309             # three count = 3*(2*4^level - 2)/3 + 2
310             # = 2*4^level
311             # 43-11 = 32
312             # 172-44 = 128
313              
314             # getting level+2 and len = 4^(level+2)
315 8         16 my ($len,$level) = round_down_pow(6*$n-2, 4);
316 8         72 my $three_parts = $len/8;
317              
318             ### all breakdown ...
319             ### $level
320             ### $len
321             ### $three_parts
322             ### Nlevel base: ($len+2)/6
323              
324 8         14 (my $quadrant, $n) = _divrem ($n-($len+2)/6, $three_parts);
325             ### $quadrant
326             ### n remainder: $n
327             ### assert: $quadrant >= 0
328             ### assert: $quadrant <= 3
329              
330             # quarter middle
331             # Nquarter = (2*4^level - 2)/3 = 2,10,42
332 8         10 $n += ($len/8-2)/3;
333 8         7 $hdx = $quadrant_to_hdx[$quadrant];
334 8         8 $vdy = $quadrant_to_vdy[$quadrant];
335             ### n in quarter: $n
336             }
337              
338             # quarter first of a replication level
339             # Nlevel = 4*(2*4^level - 2)/3 + 2
340             # = (8*4^level - 8)/3 + 2
341             # = (8*4^level - 8 + 6)/3
342             # = (8*4^level - 2)/3 2,10,42
343             # 3N = 8*4^level-2
344             # 8*4^level = 3N+2
345             # 4^(level+2) = 6N+4
346             #
347             # using level+1
348             # Nlevel = (8*4^level - 2)/3
349             # = (2*4^(level+1) - 2)/3
350              
351              
352             # getting level+2 and 16*len
353 49         192 my ($len,$level) = round_down_pow(6*$n+4, 4);
354 49         364 my $part_n = (2*$len-2)/3;
355             ### $level
356             ### $part_n
357              
358 49         46 $len = 2**$level;
359 49         76 for ( ;
360             $level-- >= 0;
361             $len /= 2, $part_n = ($part_n-2)/4) {
362              
363             ### at: "x=$x,y=$y level=$level hxy=$hdx,$hdy vxy=$vdx,$vdy n=$n"
364             ### $len
365             ### $part_n
366             ### assert: $len == 2 ** ($level+1)
367             ### assert: $part_n == (2 * 4 ** ($level+1) - 2)/3
368              
369 145 100       186 if ($n < $part_n) {
370             ### part 0, no change ...
371 55         99 next;
372             }
373              
374 90         74 $n -= $part_n;
375 90         74 $x += $len * ($hdx + $vdx); # diagonal
376 90         67 $y += $len * ($hdy + $vdy);
377              
378 90 100       112 if ($n == 0) {
379             ### toothpick A ...
380 25         22 last;
381             }
382 65 100       81 if ($n == 1) {
383             ### toothpick B ...
384 24         13 $x += $vdx;
385 24         17 $y += $vdy;
386 24         21 last;
387             }
388 41         30 $n -= 2;
389              
390 41 100       52 if ($n < $part_n) {
391             ### part 1, rotate ...
392 16         11 $x -= $hdx; # offset
393 16         16 $y -= $hdy;
394 16         19 ($hdx,$hdy, $vdx,$vdy) # rotate 90 in direction v toward h
395             = (-$vdx,-$vdy, $hdx,$hdy);
396 16         30 next;
397             }
398 25         18 $n -= $part_n;
399              
400 25 100       32 if ($n < $part_n) {
401             ### part 2 ...
402 9         16 next;
403             }
404 16         15 $n -= $part_n;
405              
406             ### part 3, mirror ...
407 16         13 $hdx = -$hdx;
408 16         27 $hdy = -$hdy;
409             }
410              
411             ### assert: $n == 0 || $n == 1
412              
413             ### final: "x=$x y=$y"
414 49         82 return ($x,$y);
415             }
416              
417             sub xy_to_n {
418 0     0 1 0 my ($self, $x, $y) = @_;
419             ### ToothpickReplicate xy_to_n(): "$x, $y"
420              
421 0         0 $x = round_nearest ($x);
422 0         0 $y = round_nearest ($y);
423              
424 0         0 my $parts = $self->{'parts'};
425 0   0     0 my $rotated = ($parts == 3 && $x >= 0 && $y < 0);
426 0 0       0 if ($rotated) {
427 0         0 ($x,$y) = (-$y,$x+1); # rotate +90 and shift up
428             ### rotated: "x=$x y=$y"
429             }
430              
431 0         0 my ($len,$level) = round_down_pow (max(abs($x), abs($y)-1),
432             2);
433 0 0       0 if (is_infinite($level)) {
434 0         0 return $level;
435             }
436             ### $level
437             ### $len
438              
439 0         0 my $zero = $x * 0 * $y;
440 0         0 my $n = $zero;
441              
442 0 0       0 if ($parts == 2) {
    0          
    0          
443 0 0       0 if ($x == 0) {
444 0 0       0 if ($y == 1) { return 0; }
  0         0  
445             }
446 0         0 $n += (2*$len*$len+1)/3; # +1,+3,+11,+43
447 0 0       0 if ($x < 0) {
448 0         0 $x = -$x;
449 0         0 $n += 2*$len*$len; # second quad, +2,+8,+32
450             }
451              
452             } elsif ($parts == 3) {
453             ### 3/4 ...
454 0 0       0 if ($x == 0) {
455 0 0       0 if ($y == 0) { return 0; }
  0         0  
456 0 0       0 if ($y == 1) { return 1; }
  0         0  
457             }
458 0         0 $n += (10*$len*$len+2)/3; # +4,+14,+54,+214,+854,+3414
459 0 0       0 if ($rotated) {
    0          
460 0         0 $n -= 2*$len*$len; # fourth quad, -2, -8, -32
461             } elsif ($x < 0) {
462 0         0 $x = -$x;
463 0 0       0 if ($y > 0) {
464 0         0 $n += 2*$len*$len; # second quad, +2, +8, +32
465             } else {
466 0         0 return undef; # third quad, empty
467             }
468             }
469             } elsif ($parts == 4) {
470 0 0       0 if ($x == 0) {
471 0 0       0 if ($y == 0) { return 0; }
  0         0  
472 0 0       0 if ($y == 1) { return 1; }
  0         0  
473 0 0       0 if ($y == -1) { return 2; }
  0         0  
474             }
475 0         0 $n += (2*$len*$len+1);
476 0 0       0 if ($x < 0) {
477 0         0 $x = -$x;
478 0 0       0 if ($y > 0) {
479 0         0 $n += 2*$len*$len; # second quad, +2, +8, +32
480             } else {
481 0         0 $n += 4*$len*$len; # third quad, +4,+16
482 0         0 $y = -$y;
483             }
484             } else {
485 0 0       0 if ($y < 0) {
486 0         0 $n += 6*$len*$len; # fourth quad
487 0         0 $y = -$y;
488             }
489             }
490             }
491              
492             # 2^(level+1)-1
493             # v
494             # +-----------+---------+
495             # | | | <- 2^(level+1)
496             # | 3 2 |
497             # | mirror same |
498             # | --B-- | <- 2^level + 1
499             # | | |
500             # +-- A --+ <- 2^level
501             # | |
502             # 1 |
503             # rot |
504             # 0 +90 |
505             # | |
506             # +-----------+
507             # ^
508             # 2^level
509              
510 0         0 my $part_n = (2*$len*$len - 2) / 3;
511             ### $part_n
512              
513 0         0 while ($level-- > 0) {
514             ### at: "x=$x,y=$y len=$len part_n=$part_n n=$n"
515             ### assert: $len == 2 ** ($level+1)
516             ### assert: $part_n == (2 * 4 ** ($level+1) - 2)/3
517              
518 0 0       0 if ($x == $len) {
519 0 0       0 if ($y == $len) {
520             ### toothpick A ...
521 0         0 return $n + $part_n;
522             }
523 0 0       0 if ($y == $len+1) {
524             ### toothpick B ...
525 0         0 return $n + $part_n + 1;
526             }
527             }
528              
529 0 0       0 if ($y <= $len) {
530 0 0       0 if ($x < $len) {
531             ### part 0 ...
532             } else {
533             ### part 1, rotate ...
534 0         0 $n += $part_n + 2;
535 0         0 ($x,$y) = ($len-$y,$x-$len+1); # shift, rotate +90
536             }
537             } else {
538 0         0 $y -= $len;
539 0 0       0 if ($x > $len) {
540             ### part 2 ...
541 0         0 $n += 2*$part_n + 2;
542 0         0 $x -= $len;
543             } else {
544             ### part 3 ...
545 0         0 $n += 3*$part_n + 2;
546 0         0 $x = $len-$x; # mirror
547             }
548             }
549              
550 0         0 $len /= 2;
551 0         0 $part_n = ($part_n-2)/4;
552             }
553              
554             ### end loop: "x=$x y=$y n=$n"
555              
556 0 0       0 if ($x == 1) {
557 0 0       0 if ($y == 1) {
    0          
558 0         0 return $n;
559             } elsif ($y == 2) {
560 0         0 return $n + 1;
561             }
562             }
563              
564 0         0 return undef;
565             }
566              
567             #------------------------------------------------------------------------------
568             # levels
569              
570             # parts=1
571             # LevelPoints[k] = 4*LevelPoints[k] + 2 starting LevelPoints[0] = 2
572             # LevelPoints[k] = 2 + 2*4 + 2*4^2 + ... + 2*4^(k-1) + 4^k*LevelPoints[0]
573             # LevelPoints[k] = 2 + 2*4 + 2*4^2 + ... + 2*4^(k-1) + 2*4^k
574             # LevelPoints[k] = 2*(4^(k+1) - 1)/3
575              
576             my %level_to_n_range = (4 => -2,
577             3 => -3,
578             2 => -4,
579             1 => -5,
580             );
581             sub level_to_n_range {
582 9     9 1 266 my ($self, $level) = @_;
583 9         27 return (0,
584             (4**($level+1) * (2*$self->{'parts'})
585             + $level_to_n_range{$self->{'parts'}}) / 3);
586             }
587             sub n_to_level {
588 0     0 1   my ($self, $n) = @_;
589 0 0         if ($n < 0) { return undef; }
  0            
590 0 0         if (is_infinite($n)) { return $n; }
  0            
591 0           $n = round_nearest($n) + 1;
592 0           _divrem_mutate ($n, 2*$self->{'parts'});
593 0           my ($pow, $exp) = round_down_pow (4*$n - $level_to_n_range{$self->{'parts'}}, 2);
594 0           return $exp + 1;
595             }
596              
597             # return $remainder, modify $n
598             # the scalar $_[0] is modified, but if it's a BigInt then a new BigInt is made
599             # and stored there, the bigint value is not changed
600             sub _divrem_mutate {
601 0     0     my $d = $_[1];
602 0           my $rem;
603 0 0 0       if (ref $_[0] && $_[0]->isa('Math::BigInt')) {
604 0           ($_[0], $rem) = $_[0]->copy->bdiv($d); # quot,rem in array context
605 0 0 0       if (! ref $d || $d < 1_000_000) {
606 0           return $rem->numify; # plain remainder if fits
607             }
608             } else {
609 0           $rem = $_[0] % $d;
610 0           $_[0] = int(($_[0]-$rem)/$d); # exact division stays in UV
611             }
612 0           return $rem;
613             }
614              
615             #------------------------------------------------------------------------------
616             1;
617             __END__