File Coverage

blib/lib/Math/PlanePath/ToothpickUpist.pm
Criterion Covered Total %
statement 91 250 36.4
branch 13 90 14.4
condition 4 37 10.8
subroutine 23 33 69.7
pod 12 12 100.0
total 143 422 33.8


line stmt bran cond sub pod time code
1             # Copyright 2012, 2013, 2014 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             #
20             # A151567 four copies of leftist toothpicks
21             # becomes 2*left(n)+2*left(n+1)-4n-1 undoubling diagonals
22             #
23             # A151565 ,1,1,2,2,2,2, 4, 4, 2, 2,4,4,4,4,8,8,2,2,4,4,4,4,8,8,4,4,8,8,8,8,16,
24             # A151566 ,0,1,2,4,6,8,10,14,18,20,22,26,30,34,38,46,54,56,58,62,66,70,74,82,90
25              
26             # A175099,A160018 leftist closed rectangles
27              
28             package Math::PlanePath::ToothpickUpist;
29 1     1   1602 use 5.004;
  1         4  
  1         56  
30 1     1   8 use strict;
  1         2  
  1         76  
31              
32 1     1   8 use vars '$VERSION', '@ISA';
  1         2  
  1         101  
33             $VERSION = 16;
34 1     1   1035 use Math::PlanePath;
  1         8408  
  1         201  
35             @ISA = ('Math::PlanePath');
36              
37              
38             # return $remainder, modify $n
39             # the scalar $_[0] is modified, but if it's a BigInt then a new BigInt is made
40             # and stored there, the bigint value is not changed
41             sub _divrem_mutate {
42 7     7   9 my $d = $_[1];
43 7         7 my $rem;
44 7 50 33     25 if (ref $_[0] && $_[0]->isa('Math::BigInt')) {
45 0         0 ($_[0], $rem) = $_[0]->copy->bdiv($d); # quot,rem in array context
46 0 0 0     0 if (! ref $d || $d < 1_000_000) {
47 0         0 return $rem->numify; # plain remainder if fits
48             }
49             } else {
50 7         11 $rem = $_[0] % $d;
51 7         15 $_[0] = int(($_[0]-$rem)/$d); # exact division stays in UV
52             }
53 7         10 return $rem;
54             }
55              
56              
57             use Math::PlanePath::Base::Generic
58 1         73 'is_infinite',
59 1     1   10 'round_nearest';
  1         2  
60             use Math::PlanePath::Base::Digits
61 1         104 'round_down_pow',
62             'bit_split_lowtohigh',
63 1     1   779 'digit_join_lowtohigh';
  1         2044  
64              
65             # uncomment this to run the ### lines
66             # use Smart::Comments;
67              
68              
69 1     1   8 use constant default_n_start => 0;
  1         2  
  1         653  
70 1     1   8 use constant class_x_negative => 1;
  1         2  
  1         61  
71 1     1   6 use constant class_y_negative => 0;
  1         2  
  1         67  
72 1     1   6 use constant x_negative_at_n => 2;
  1         2  
  1         59  
73 1     1   6 use constant sumxy_minimum => 0; # triangular X>=-Y
  1         1  
  1         58  
74 1     1   6 use constant diffxy_maximum => 0; # triangular X<=Y so X-Y<=0
  1         1  
  1         57  
75 1     1   6 use constant dy_minimum => 0; # across rows dY=0
  1         2  
  1         64  
76 1     1   6 use constant dy_maximum => 1; # then up dY=1 at end
  1         1  
  1         61  
77 1     1   6 use constant tree_num_children_list => (0,1,2);
  1         1  
  1         75  
78 1     1   6 use constant dir_maximum_dxdy => (-1,0); # West
  1         1  
  1         1062  
79              
80              
81             #------------------------------------------------------------------------------
82             sub new {
83 4     4 1 847 my $self = shift->SUPER::new(@_);
84 4 50       46 if (! defined $self->{'n_start'}) {
85 4         27 $self->{'n_start'} = $self->default_n_start;
86             }
87 4         11 return $self;
88             }
89              
90             sub n_to_xy {
91 0     0 1 0 my ($self, $n) = @_;
92             ### ToothpickUpist n_to_xy(): $n
93              
94             # written as $n-n_start() rather than "-=" so as to provoke an
95             # uninitialized value warning if $n==undef
96 0         0 $n = $n - $self->{'n_start'}; # N=0 basis
97              
98 0 0       0 if ($n < 0) {
99 0         0 return;
100             }
101 0 0 0     0 if ($n == 0 || is_infinite($n)) {
102 0         0 return ($n,$n);
103             }
104              
105             # this frac behaviour unspecified yet
106             {
107 0         0 my $int = int($n);
  0         0  
108             ### $int
109             ### $n
110 0 0       0 if ($n != $int) {
111 0         0 my $frac = $n - $int; # inherit possible BigFloat
112 0         0 $int += $self->{'n_start'};
113 0         0 my ($x1,$y1) = $self->n_to_xy($int);
114 0         0 my ($x2,$y2) = $self->n_to_xy($int+1);
115 0         0 my $dx = $x2-$x1;
116 0         0 my $dy = $y2-$y1;
117 0         0 return ($frac*$dx + $x1, $frac*$dy + $y1);
118             }
119 0         0 $n = $int; # BigFloat int() gives BigInt, use that
120             }
121             ### $n
122              
123 0         0 my ($depthbits, $lowbit, $ndepth) = _n0_to_depthbits($n);
124             ### $depthbits
125             ### $ndepth
126             ### n remainder: $n-$ndepth
127              
128 0         0 my @nbits = bit_split_lowtohigh($n-$ndepth); # offset into row
129              
130             ### @nbits
131             ### $lowbit
132              
133             # Where there's a 0-bit in the depth remains a 0-bit.
134             # Where there's a 1-bit in the depth takes a bit from Noffset.
135             # Small Noffset has less bits than the depth 1s, hence "|| 0".
136             #
137 0 0 0     0 my @xbits = map {$_ && (shift @nbits || 0)} @$depthbits;
  0         0  
138             ### @xbits
139              
140 0         0 my $zero = $n * 0;
141 0         0 my $x = digit_join_lowtohigh (\@xbits, 2, $zero);
142 0         0 my $y = digit_join_lowtohigh ($depthbits, 2, $zero);
143              
144             ### Y without lowbit: $y
145              
146 0         0 return (2*$x-$y, # triangular style
147             $y + $lowbit);
148             }
149              
150             sub xy_to_n {
151 0     0 1 0 my ($self, $x, $y) = @_;
152             ### ToothpickUpist xy_to_n(): "$x, $y"
153              
154 0         0 $y = round_nearest ($y);
155 0         0 $x = round_nearest($x);
156              
157             # odd points X!=Ymod2 are the second copy of the triangle, go to Y-1 for them
158 0         0 $x += $y;
159 0         0 my $lowbit = _divrem_mutate ($x, 2);
160 0         0 $y -= $lowbit;
161             ### odd adjusted xy: "$x,$y"
162              
163 0         0 return _right_xy_to_n ($self, $x,$y, $lowbit);
164             }
165              
166             # with X,Y in the align="right" style,
167             #
168             # |
169             sub _right_xy_to_n {
170 7     7   8 my ($self, $x, $y, $lowbit) = @_;
171             ### _right_xy_to_n(): "x=$x y=$y lowbit=$lowbit"
172              
173 7 50 33     66 unless ($x >= 0 && $x <= $y && $y >= 0) {
      33        
174             ### outside horizontal row range ...
175 0         0 return undef;
176             }
177 7 50       17 if (is_infinite($y)) {
178 0         0 return $y;
179             }
180              
181 7         45 my $zero = ($y * 0);
182 7         8 my $n = $zero; # inherit bignum 0
183 7         7 my $npower = $zero+2; # inherit bignum 2
184              
185 7         20 my @xbits = bit_split_lowtohigh($x);
186 7         45 my @depthbits = bit_split_lowtohigh($y);
187              
188 7         91 my @nbits; # N offset into row
189 7         18 foreach my $i (0 .. $#depthbits) { # x,y bits low to high
190 28 100       38 if ($depthbits[$i]) {
191 7         9 $n = 2*$n + $npower;
192 7   50     33 push @nbits, $xbits[$i] || 0; # low to high
193             } else {
194 21 50       36 if ($xbits[$i]) {
195 0         0 return undef;
196             }
197             }
198 28         39 $npower *= 3;
199             }
200              
201 7 50       18 if ($lowbit) {
202 0         0 push @nbits, 1;
203             }
204              
205             ### n at left end of y row: $n
206             ### n offset for x: @nbits
207             ### total: $n + digit_join_lowtohigh(\@nbits,2,$zero) + $self->{'n_start'}
208              
209 7         24 return $n + digit_join_lowtohigh(\@nbits,2,$zero) + $self->{'n_start'};
210             }
211              
212             # not exact
213             sub rect_to_n_range {
214 0     0 1 0 my ($self, $x1,$y1, $x2,$y2) = @_;
215             ### ToothpickUpist rect_to_n_range(): "$x1,$y1, $x2,$y2"
216              
217 0         0 $y1 = round_nearest ($y1);
218 0         0 $y2 = round_nearest ($y2);
219 0 0       0 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) }
  0         0  
220              
221 0         0 $x1 = round_nearest ($x1);
222 0         0 $x2 = round_nearest ($x2);
223 0 0       0 if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) }
  0         0  
224              
225 0 0       0 if ($y2 < 0) {
226             ### all negative ...
227 0         0 return (1, 0);
228             }
229 0         0 $y1 -= 1;
230 0 0       0 if ($y1 < 0) {
231 0         0 $y1 = 0;
232             }
233              
234             ### range using: "y1=$y1 y2=$y2"
235              
236 0         0 return (_right_xy_to_n($self, 0,$y1, 0),
237             _right_xy_to_n($self, $y2,$y2, 1));
238             }
239              
240              
241             #------------------------------------------------------------------------------
242 1     1   8 use constant tree_num_roots => 1;
  1         2  
  1         1796  
243              
244             sub tree_n_num_children {
245 0     0 1 0 my ($self, $n) = @_;
246              
247 0         0 $n = $n - $self->{'n_start'}; # N=0 basis
248 0 0 0     0 if (is_infinite($n) || $n < 0) {
249 0         0 return undef;
250             }
251              
252 0         0 my ($depthbits, $lowbit, $ndepth) = _n0_to_depthbits($n);
253 0 0       0 if (! $lowbit) {
254 0         0 return 1;
255             }
256 0 0       0 unless (shift @$depthbits) { # low bit above $lowbit doubling
257             # Depth even (or zero), two children under every point.
258 0         0 return 2;
259             }
260              
261             # Depth odd, single child under some or all points.
262             # When depth==1mod4 it's all points, when depth has more than one
263             # trailing 1-bit then it's only some points.
264             #
265 0         0 $n -= $ndepth; # Noffset into row
266 0         0 my $repbit = _divrem_mutate($n,2);
267 0         0 while (shift @$depthbits) { # low to high
268 0 0       0 if (_divrem_mutate($n,2) != $repbit) {
269 0         0 return 0;
270             }
271             }
272 0         0 return 1;
273             }
274              
275             sub tree_n_children {
276 0     0 1 0 my ($self, $n) = @_;
277             ### tree_n_children(): $n
278              
279 0         0 $n = $n - $self->{'n_start'}; # N=0 basis
280 0 0 0     0 if (is_infinite($n) || $n < 0) {
281 0         0 return;
282             }
283              
284 0         0 my ($depthbits, $lowbit, $ndepth, $nwidth) = _n0_to_depthbits($n);
285 0 0       0 if (! $lowbit) {
286             ### doubled to children at nwidth below ...
287 0         0 return ($n + $nwidth);
288             }
289              
290 0         0 $n -= $ndepth; # Noffset into row
291              
292 0 0       0 if (shift @$depthbits) {
293             # Depth odd, single child under some or all points.
294             # When depth==1mod4 it's all points, when depth has more than one
295             # trailing 1-bit then it's only some points.
296 0         0 while (shift @$depthbits) { # depth==3mod4 or more low 1s
297 0         0 my $repbit = _divrem_mutate($n,2);
298 0 0       0 if (($n % 2) != $repbit) {
299 0         0 return;
300             }
301             }
302 0         0 return $n + $ndepth+$nwidth + $self->{'n_start'};
303              
304             } else {
305             # Depth even (or zero), two children under every point.
306 0         0 $n = 2*$n + $ndepth+$nwidth + $self->{'n_start'};
307 0         0 return ($n,$n+1);
308             }
309             }
310              
311             sub tree_n_parent {
312 0     0 1 0 my ($self, $n) = @_;
313              
314 0 0       0 my ($x,$y) = $self->n_to_xy($n)
315             or return undef;
316              
317 0 0       0 if (($x%2) != ($y%2)) {
318             ### odd, directly down ...
319 0         0 return $self->xy_to_n($x,$y-1);
320             }
321              
322             ### even, to one side or the other ...
323 0         0 my $n_parent = $self->xy_to_n($x-1, $y);
324 0 0       0 if (defined $n_parent) {
325 0         0 return $n_parent;
326             }
327 0         0 return $self->xy_to_n($x+1,$y);
328             }
329              
330             sub tree_n_to_depth {
331 1     1 1 51 my ($self, $n) = @_;
332             ### ToothpickUpist n_to_depth(): $n
333 1         3 $n = $n - $self->{'n_start'};
334 1 50       5 unless ($n >= 0) {
335 0         0 return undef; # negatives, -infinity, NaN
336             }
337 1 50       4 if (is_infinite($n)) {
338 1         9 return $n; # +infinity
339             }
340 0         0 my ($depthbits, $lowbit) = _n0_to_depthbits($n);
341 0         0 unshift @$depthbits, $lowbit;
342 0         0 return digit_join_lowtohigh ($depthbits, 2, $n*0);
343             }
344             sub tree_depth_to_n {
345 8     8 1 68 my ($self, $depth) = @_;
346             ### tree_depth_to_n(): $depth
347 8 50       16 if ($depth >= 0) {
348             # $depth==+infinity becomes nan from divrem, prefer to return N=+infinity
349             # for +inf depth
350 8 100       26 if (is_infinite($depth)) {
351 1         10 return $depth;
352             }
353 7         55 my $lowbit = _divrem_mutate($depth,2);
354 7         17 return _right_xy_to_n($self,0,$depth, $lowbit);
355             } else {
356 0         0 return undef;
357             }
358             }
359              
360             sub tree_n_to_subheight {
361 0     0 1 0 my ($self, $n) = @_;
362             ### ToothpickUpist tree_n_to_subheight(): $n
363              
364 0         0 $n = $n - $self->{'n_start'};
365 0 0 0     0 if (is_infinite($n) || $n < 0) {
366 0         0 return undef;
367             }
368 0         0 my ($depthbits, $lowbit, $ndepth) = _n0_to_depthbits($n);
369 0         0 $n -= $ndepth; # remaining offset into row
370 0         0 my @nbits = bit_split_lowtohigh($n);
371              
372             ### $lowbit
373             ### $depthbits
374              
375 0   0     0 my $target = $nbits[0] || 0;
376 0         0 foreach my $i (0 .. $#$depthbits) {
377 0 0       0 unless ($depthbits->[$i] ^= 1) { # flip 0<->1, at original==1 take nbit
378 0 0 0     0 if ((shift @nbits || 0) != $target) {
379 0         0 unshift @$depthbits, 1-$lowbit;
380 0         0 $#$depthbits = $i;
381             ### $depthbits
382 0         0 return digit_join_lowtohigh($depthbits, 2, $n*0);
383             }
384             }
385             }
386 0         0 return undef; # first or last of row, infinite
387             }
388              
389             sub _EXPERIMENTAL__tree_n_to_leafdist {
390 0     0   0 my ($self, $n) = @_;
391             ### _EXPERIMENTAL__tree_n_to_leafdist(): $n
392              
393 0         0 $n = $n - $self->{'n_start'}; # N=0 basis
394 0 0 0     0 if (is_infinite($n) || $n < 0) {
395 0         0 return undef;
396             }
397              
398             # depth bits leafdist
399             # 0 0,0 7
400             # 1 0,1 6
401             # 2 1,0 5
402             # 3 1,1 4
403             # 4 1,0,0 3
404             # 5 1,0,1 2
405             # 6 1,1,0 1 or 9
406             # 7 1,1,1 0 or 8
407             # ignore $lowbit until last, bits above same as SierpinskiTriangle
408             #
409 0         0 my ($depthbits, $lowbit, $ndepth) = _n0_to_depthbits($n);
410 0         0 $lowbit = 1-$lowbit;
411              
412 0   0     0 my $ret = 6 - 2*((shift @$depthbits)||0);
413 0 0       0 if (shift @$depthbits) { $ret -= 4; }
  0         0  
414             ### $ret
415 0 0       0 if ($ret) {
416 0         0 return $ret + $lowbit;
417             }
418              
419 0         0 $n -= $ndepth;
420             ### Noffset into row: $n
421              
422             # Low bits of Nrem unchanging while trailing 1-bits in @depthbits,
423             # to distinguish between leaf or non-leaf. Same as tree_n_children().
424             #
425 0         0 my $repbit = _divrem_mutate($n,2); # low bit of $n
426             ### $repbit
427 0         0 do {
428             ### next bit: $n%2
429 0 0       0 if (_divrem_mutate($n,2) != $repbit) { # bits of $n offset low to high
430 0         0 return $lowbit; # is a leaf
431             }
432             } while (shift @$depthbits);
433 0         0 return 8+$lowbit; # is a non-leaf
434             }
435              
436             # Ndepth = 2 * ( 3^a first N at this depth
437             # + 2 * 3^b
438             # + 2^2 * 3^c
439             # + 2^3 * 3^d
440             # + ... )
441              
442             sub _n0_to_depthbits {
443 0     0   0 my ($n) = @_;
444             ### _n0_to_depthbits(): $n
445              
446 0 0       0 if ($n == 0) {
447 0         0 return ([], 0, 0, 1);
448             }
449              
450 0         0 my ($nwidth, $bitpos) = round_down_pow ($n/2, 3);
451             ### nwidth power-of-3: $nwidth
452             ### $bitpos
453              
454 0         0 $nwidth *= 2; # two of each row
455              
456 0         0 my @depthbits;
457 0         0 my $ndepth = 0;
458 0         0 for (;;) {
459             ### at: "n=$n nwidth=$nwidth bitpos=$bitpos depthbits=".join(',',map{$_||0}@depthbits)
460              
461 0 0       0 if ($n >= $ndepth + $nwidth) {
462 0         0 $depthbits[$bitpos] = 1;
463 0         0 $ndepth += $nwidth;
464 0         0 $nwidth *= 2;
465             } else {
466 0         0 $depthbits[$bitpos] = 0;
467             }
468 0 0       0 last unless --$bitpos >= 0;
469 0         0 $nwidth /= 3;
470             }
471              
472             # Nwidth = 2**count1bits(depth)
473             ### assert: $nwidth == 2*(1 << scalar(grep{$_}@depthbits))
474              
475             # first or second of the two of each row
476 0         0 $nwidth /= 2;
477 0 0       0 my $lowbit = ($n >= $ndepth + $nwidth ? 1 : 0);
478 0 0       0 if ($lowbit) {
479 0         0 $ndepth += $nwidth;
480             }
481             ### final depthbits: join(',',@depthbits)
482              
483 0         0 return (\@depthbits, $lowbit, $ndepth, $nwidth);
484             }
485              
486             #------------------------------------------------------------------------------
487             # levels
488              
489             sub level_to_n_range {
490 11     11 1 750 my ($self, $level) = @_;
491 11         38 return (0, 2* 3**$level - 1);
492             }
493             sub n_to_level {
494 0     0 1   my ($self, $n) = @_;
495 0 0         if ($n < 0) { return undef; }
  0            
496 0 0         if (is_infinite($n)) { return $n; }
  0            
497 0           $n = round_nearest($n);
498 0           _divrem_mutate ($n, 2);
499 0           my ($pow, $exp) = round_down_pow ($n, 3);
500 0           return $exp + 1;
501             }
502              
503             #------------------------------------------------------------------------------
504             1;
505             __END__