File Coverage

blib/lib/Math/PlanePath/AlternatePaperMidpoint.pm
Criterion Covered Total %
statement 133 148 89.8
branch 41 54 75.9
condition 10 17 58.8
subroutine 18 22 81.8
pod 10 10 100.0
total 212 251 84.4


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 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             # math-image --path=AlternatePaperMidpoint,arms=8 --all --output=numbers_dash
20             # math-image --path=AlternatePaperMidpoint --lines --scale=20
21             #
22             # A334576 ~/OEIS/a334576.gp.txt
23             # Remy Sigrist, building vector of coordinates by segment expansion
24              
25              
26             package Math::PlanePath::AlternatePaperMidpoint;
27 1     1   9576 use 5.004;
  1         10  
28 1     1   5 use strict;
  1         2  
  1         39  
29 1     1   6 use List::Util 'min'; # 'max'
  1         2  
  1         166  
30             *max = \&Math::PlanePath::_max;
31              
32 1     1   8 use vars '$VERSION', '@ISA';
  1         2  
  1         69  
33             $VERSION = 129;
34 1     1   706 use Math::PlanePath;
  1         3  
  1         30  
35 1     1   432 use Math::PlanePath::Base::NSEW;
  1         3  
  1         39  
36             @ISA = ('Math::PlanePath::Base::NSEW',
37             'Math::PlanePath');
38              
39             use Math::PlanePath::Base::Generic
40 1         49 'is_infinite',
41 1     1   6 'round_nearest';
  1         2  
42             use Math::PlanePath::Base::Digits
43 1         75 'round_down_pow',
44             'digit_split_lowtohigh',
45 1     1   489 'digit_join_lowtohigh';
  1         2  
46             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
47              
48 1     1   623 use Math::PlanePath::AlternatePaper;
  1         3  
  1         58  
49              
50             # uncomment this to run the ### lines
51             #use Smart::Comments;
52              
53              
54 1         59 use constant parameter_info_array => [ { name => 'arms',
55             share_key => 'arms_8',
56             display => 'Arms',
57             type => 'integer',
58             minimum => 1,
59             maximum => 8,
60             default => 1,
61             width => 1,
62             description => 'Arms',
63 1     1   8 } ];
  1         2  
64              
65 1     1   6 use constant n_start => 0;
  1         2  
  1         1245  
66              
67             sub x_negative {
68 6     6 1 81 my ($self) = @_;
69 6         13 return ($self->{'arms'} >= 3);
70             }
71             sub y_negative {
72 6     6 1 323 my ($self) = @_;
73 6         19 return ($self->{'arms'} >= 5);
74             }
75             {
76             my @x_negative_at_n = (undef,
77             undef,undef,11,3,
78             3,3,3,3);
79             sub x_negative_at_n {
80 0     0 1 0 my ($self) = @_;
81 0         0 return $x_negative_at_n[$self->{'arms'}];
82             }
83             }
84             {
85             my @y_negative_at_n = (undef,
86             undef,undef,undef,undef,
87             24,11,12,7);
88             sub y_negative_at_n {
89 0     0 1 0 my ($self) = @_;
90 0         0 return $y_negative_at_n[$self->{'arms'}];
91             }
92             }
93              
94             sub sumxy_minimum {
95 0     0 1 0 my ($self) = @_;
96 0 0       0 return ($self->arms_count <= 3
97             ? 0 # 1,2,3 arms above X=-Y diagonal
98             : undef);
99             }
100             sub diffxy_minimum {
101 0     0 1 0 my ($self) = @_;
102 0 0       0 return ($self->arms_count == 1
103             ? 0 # 1 arms right of X=Y diagonal
104             : undef);
105             }
106              
107              
108             #------------------------------------------------------------------------------
109              
110             sub new {
111 25     25 1 2550 my $self = shift->SUPER::new(@_);
112 25   100     136 $self->{'arms'} = max(1, min(8, $self->{'arms'} || 1));
113 25         55 return $self;
114             }
115              
116             # +-----------+ states
117             # |\ -------/|
118             # | \ \ 4 / |
119             # |^ \ \ / |
120             # || \ v / /||
121             # || \ / / ||
122             # ||8 / * /12||
123             # || / / \ ||
124             # ||/ / ^ \ ||
125             # | / \ \ v|
126             # | / 0 \ \ |
127             # |/ ------ \|
128             # +-----------+
129             #
130             # + state=0 digits
131             # /|\
132             # / | \
133             # / | \
134             # /\ 1|3 /\
135             # / \ | / \
136             # / 0 \|/ 2 \
137             # +------+------+
138              
139             my @next_state = (0, 12, 0, 8, # 0 forward
140             4, 8, 4, 12, # 4 forward NW
141             4, 8, 0, 8, # 8 reverse
142             0, 12, 4, 12, # 12 reverse NE
143             );
144             my @digit_to_x = (0,0,1,1,
145             1,1,0,0,
146             0,0,0,0,
147             1,1,1,1,
148             );
149             my @digit_to_y = (0,0,0,0,
150             1,1,1,1,
151             0,0,1,1,
152             1,1,0,0,
153             );
154              
155             sub n_to_xy {
156 2659     2659 1 14153 my ($self, $n) = @_;
157             ### AlternatePaperMidpoint n_to_xy(): $n
158              
159 2659 50       5122 if ($n < 0) { return; }
  0         0  
160 2659 50       5701 if (is_infinite($n)) { return ($n, $n); }
  0         0  
161              
162             {
163 2659         4786 my $int = int($n);
  2659         3916  
164 2659 100       4893 if ($n != $int) {
165 6         29 my ($x1,$y1) = $self->n_to_xy($int);
166 6         17 my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'});
167 6         15 my $frac = $n - $int; # inherit possible BigFloat
168 6         10 my $dx = $x2-$x1;
169 6         9 my $dy = $y2-$y1;
170 6         24 return ($frac*$dx + $x1, $frac*$dy + $y1);
171             }
172 2653         4048 $n = $int; # BigFloat int() gives BigInt, use that
173             }
174              
175 2653         3835 my $zero = ($n * 0); # inherit bignum 0
176 2653         5728 my $arm = _divrem_mutate ($n, $self->{'arms'});
177             ### $arm
178             ### $n
179              
180 2653         5528 my @digits = digit_split_lowtohigh($n,4);
181 2653         4082 my $state = my $dirstate = 0;
182              
183 2653         4006 my @x;
184             my @y;
185 2653         5151 foreach my $i (reverse 1 .. scalar(@digits)) {
186 7113         10566 my $digit = $digits[$i-1]; # high to low, all digits
187 7113         9887 $state += $digit;
188 7113 100       12200 if ($digit != 3) {
189 5207         7090 $dirstate = $state;
190             }
191 7113         10558 $x[$i] = $digit_to_x[$state]; # high to low, leaving one lowest
192 7113         10191 $y[$i] = $digit_to_y[$state];
193 7113         11570 $state = $next_state[$state];
194             }
195              
196 2653         4448 $x[0] = $digit_to_x[$state]; # state=4,12 increment
197 2653         4144 $y[0] = $digit_to_y[$state + 3]; # state=4,8 increment
198              
199 2653         5691 my $x = digit_join_lowtohigh(\@x,2,$zero);
200 2653         5512 my $y = digit_join_lowtohigh(\@y,2,$zero);
201              
202             ### final: "x=$x,y=$y state=$state"
203              
204 2653 100       5437 if ($arm & 1) {
205 1444         2677 ($x,$y) = ($y+1,$x+1); # transpose and offset
206             }
207 2653 100       4900 if ($arm & 2) {
208 1307         2364 ($x,$y) = (-$y,$x+1); # rotate +90 and offset
209             }
210 2653 100       4791 if ($arm & 4) {
211 1046         1504 $x = -1 - $x; # rotate 180 and offset
212 1046         1527 $y = 1 - $y;
213             }
214              
215             # ### rotated return: "$x,$y"
216 2653         6467 return ($x,$y);
217             }
218              
219             # | |
220             # 64-65-66 71-72-73-74 95
221             # | |
222             # 63 98-97-96
223             # | |
224             # 20-21 62 99
225             # | | |
226             # 19 22 61-60-59
227             # | | |
228             # 16-17-18 23 56-57-58
229             # | | |
230             # 15 26-25-24 55 50-49-48-47
231             # | | | | |
232             # 4--5 14 27-28-29 54 51 36-37 46
233             # | | | | | | | | |
234             # 3 6 13-12-11 30 53-52 35 38 45-44-43
235             # | | | | | | |
236             # 0--1--2 7--8--9-10 31-32-33-34 39-40-41-42
237             #
238             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
239              
240             # 43-35 42-50-58 57-49-41
241             # | | | |
242             # 91-99 51 27 34-26-18 17-25-33
243             # | | | | |
244             # 83-75-67-59 19-11--3 10 9 32-40
245             # | | | |
246             # 84-76-68-60 20-12--4 2 1 24 48 96-88
247             # | | | | | |
248             # 92 52 28 5 6 0--8-16 56-64-72-80
249             # | | | |
250             # 44-36 13 14 7-15-23 63-71-79-87
251             # | | | | |
252             # 37-29-21 22-30-38 31 55 95
253             # | | | |
254             # 45-53-61 62-54-46 39-47
255             # | |
256             # 69 70
257              
258             sub xy_to_n {
259 44     44 1 554 my ($self, $x, $y) = @_;
260             ### AlternatePaperMidpoint xy_to_n(): "$x, $y"
261              
262 44         98 $x = round_nearest($x);
263 44         99 $y = round_nearest($y);
264              
265 44 50       85 if (is_infinite($x)) {
266 0         0 return $x; # infinity
267             }
268 44 50       100 if (is_infinite($y)) {
269 0         0 return $y; # infinity
270             }
271              
272             # arm in various octants, rotate/transpose to first
273 44         71 my $arm;
274 44 100       99 if ($y >= ($x>=0?0:2)) { # Y>=0 when X positive, Y>=2 when X negative
    100          
275 37         68 $arm = 0;
276             } else {
277             # lower arms 4,5,6,7 ...
278 7         9 $arm = 4;
279 7         12 $x = -1 - $x; # rotate 180, offset
280 7         10 $y = 1 - $y;
281             }
282 44 100       98 if ($x < ($y>0?1:0)) {
    100          
283             ### second quad arms 2,3 ...
284 8         17 ($x,$y) = ($y-1,-$x); # rotate -90, offset
285 8         15 $arm += 2;
286             }
287 44 100       117 if ($y > $x-($x%2)) {
288             ### above diagonal, arm 1 ...
289 15         29 ($x,$y) = ($y-1,$x-1); # offset and transpose
290 15         21 $arm++;
291             }
292             ### assert: $x >= 0
293             ### assert: $y >= 0
294             ### assert: $y <= $x - ($x%2)
295              
296 44 50       93 if ($arm >= $self->{'arms'}) {
297 0         0 return undef;
298             }
299              
300 44         108 my ($len, $level) = round_down_pow ($x, 2);
301 44 50       94 if (is_infinite($level)) {
302 0         0 return ($level);
303             }
304              
305             # + state=0 digits
306             # /|\
307             # / | \
308             # / | \
309             # /\ 1|3 /\
310             # / \ | / \
311             # / 0 \|/ 2 \
312             # +------+------+
313              
314             # + state=0 digits
315             # /|\
316             # / | \
317             # / | \
318             # /\ 2|0 /\
319             # / \ | / \
320             # / 3 \|/ 1 \
321             # +------+------+
322              
323 44         83 my $n = ($x * 0 * $y); # inherit bignum 0
324 44         68 my $rev = 0;
325              
326 44         63 $len *= 2;
327 44         98 while ($level-- >= 0) {
328             ### at: "xy=$x,$y rev=$rev len=$len n=".sprintf('%#x',$n)
329              
330             ### assert: $x >= 0
331             ### assert: $y >= 0
332             ### assert: $y <= $x - ($x%2)
333             ### assert: $x+$y+($x%2) < 2*$len
334              
335 223         283 my $digit;
336 223 100       370 if ($x < $len) {
337             ### diagonal: $x+$y+($x%2), $len
338 117 100       209 if ($x+$y+($x%2) < $len) {
339             ### part 0 ...
340 64         93 $digit = 0;
341             } else {
342             ### part 1 ...
343 53         129 ($x,$y) = ($y,$len-1-$x); # shift, rotate -90
344 53         73 $rev ^= 3;
345 53         74 $digit = 2; # becoming digit=1 with reverse
346             }
347             } else {
348 106         143 $x -= $len;
349             ### 2,3 ycmp: $y, $x-($x%2)
350 106 100       180 if ($y <= $x-($x%2)) {
351             ### part 2 ...
352 61         83 $digit = 2;
353             } else {
354             ### part 3 ...
355 45         87 ($x,$y) = ($len-1-$y,$x); # shift, rotate +90
356 45         67 $rev ^= 3;
357 45         62 $digit = 0; # becoming digit=3 with reverse
358             }
359             }
360             ### $digit
361              
362 223         300 $digit ^= $rev; # $digit = 3-$digit if reverse
363             ### reversed digit: $digit
364              
365 223         310 $n *= 4;
366 223         310 $n += $digit;
367 223         396 $len /= 2;
368             }
369             ### final: "xy=$x,$y rev=$rev"
370              
371             ### assert: $x == 0
372             ### assert: $y == 0
373              
374 44         123 return $n*$self->{'arms'} + $arm;
375             }
376              
377              
378             # not exact
379             sub rect_to_n_range {
380 40     40 1 3404 my ($self, $x1,$y1, $x2,$y2) = @_;
381             ### AlternatePaperMidpoint rect_to_n_range(): "$x1,$y1 $x2,$y2 arms=$self->{'arms'}"
382              
383 40         108 $x1 = round_nearest($x1);
384 40         90 $x2 = round_nearest($x2);
385 40         79 $y1 = round_nearest($y1);
386 40         83 $y2 = round_nearest($y2);
387              
388 40 50       102 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
389 40 50       76 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
390              
391 40         69 my $arms = $self->{'arms'};
392 40 50 66     253 if (($arms == 1 && $y1 > $x2) # x2,y1 bottom right corner
      66        
      33        
      66        
      33        
393             || ($arms <= 2 && $x2 < 0)
394             || ($arms <= 4 && $y2 < 0)) {
395             ### outside ...
396 0         0 return (1,0);
397             }
398              
399 40 100       176 my ($len) = round_down_pow (max ($x2,
    100          
    100          
400             ($arms >= 2 ? $y2-1 : ()),
401             ($arms >= 4 ? -1-$x1 : ()),
402             ($arms >= 6 ? -$y1 : ())),
403             2);
404 40         117 return (0, 2*$arms*$len*$len-1);
405             }
406              
407             #------------------------------------------------------------------------------
408             # levels
409              
410 1     1   9 use Math::PlanePath::DragonMidpoint;
  1         2  
  1         76  
411             *level_to_n_range = \&Math::PlanePath::DragonMidpoint::level_to_n_range;
412             *n_to_level = \&Math::PlanePath::DragonMidpoint::n_to_level;
413              
414             #------------------------------------------------------------------------------
415             1;
416             __END__