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   7694 use 5.004;
  1         9  
28 1     1   5 use strict;
  1         1  
  1         33  
29 1     1   6 use List::Util 'min'; # 'max'
  1         2  
  1         142  
30             *max = \&Math::PlanePath::_max;
31              
32 1     1   6 use vars '$VERSION', '@ISA';
  1         2  
  1         58  
33             $VERSION = 128;
34 1     1   552 use Math::PlanePath;
  1         2  
  1         27  
35 1     1   342 use Math::PlanePath::Base::NSEW;
  1         2  
  1         35  
36             @ISA = ('Math::PlanePath::Base::NSEW',
37             'Math::PlanePath');
38              
39             use Math::PlanePath::Base::Generic
40 1         42 'is_infinite',
41 1     1   5 'round_nearest';
  1         1  
42             use Math::PlanePath::Base::Digits
43 1         64 'round_down_pow',
44             'digit_split_lowtohigh',
45 1     1   377 'digit_join_lowtohigh';
  1         3  
46             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
47              
48 1     1   476 use Math::PlanePath::AlternatePaper;
  1         3  
  1         53  
49              
50             # uncomment this to run the ### lines
51             #use Smart::Comments;
52              
53              
54 1         54 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   7 } ];
  1         2  
64              
65 1     1   6 use constant n_start => 0;
  1         2  
  1         1062  
66              
67             sub x_negative {
68 6     6 1 68 my ($self) = @_;
69 6         16 return ($self->{'arms'} >= 3);
70             }
71             sub y_negative {
72 6     6 1 258 my ($self) = @_;
73 6         12 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 2193 my $self = shift->SUPER::new(@_);
112 25   100     135 $self->{'arms'} = max(1, min(8, $self->{'arms'} || 1));
113 25         47 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 12342 my ($self, $n) = @_;
157             ### AlternatePaperMidpoint n_to_xy(): $n
158              
159 2659 50       4638 if ($n < 0) { return; }
  0         0  
160 2659 50       4645 if (is_infinite($n)) { return ($n, $n); }
  0         0  
161              
162             {
163 2659         4186 my $int = int($n);
  2659         3435  
164 2659 100       4384 if ($n != $int) {
165 6         18 my ($x1,$y1) = $self->n_to_xy($int);
166 6         15 my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'});
167 6         11 my $frac = $n - $int; # inherit possible BigFloat
168 6         8 my $dx = $x2-$x1;
169 6         7 my $dy = $y2-$y1;
170 6         21 return ($frac*$dx + $x1, $frac*$dy + $y1);
171             }
172 2653         3445 $n = $int; # BigFloat int() gives BigInt, use that
173             }
174              
175 2653         3331 my $zero = ($n * 0); # inherit bignum 0
176 2653         5093 my $arm = _divrem_mutate ($n, $self->{'arms'});
177             ### $arm
178             ### $n
179              
180 2653         4788 my @digits = digit_split_lowtohigh($n,4);
181 2653         3632 my $state = my $dirstate = 0;
182              
183 2653         3450 my @x;
184             my @y;
185 2653         4563 foreach my $i (reverse 1 .. scalar(@digits)) {
186 7089         9349 my $digit = $digits[$i-1]; # high to low, all digits
187 7089         8792 $state += $digit;
188 7089 100       11487 if ($digit != 3) {
189 5186         6270 $dirstate = $state;
190             }
191 7089         9413 $x[$i] = $digit_to_x[$state]; # high to low, leaving one lowest
192 7089         9422 $y[$i] = $digit_to_y[$state];
193 7089         10344 $state = $next_state[$state];
194             }
195              
196 2653         4152 $x[0] = $digit_to_x[$state]; # state=4,12 increment
197 2653         3805 $y[0] = $digit_to_y[$state + 3]; # state=4,8 increment
198              
199 2653         5167 my $x = digit_join_lowtohigh(\@x,2,$zero);
200 2653         4986 my $y = digit_join_lowtohigh(\@y,2,$zero);
201              
202             ### final: "x=$x,y=$y state=$state"
203              
204 2653 100       4752 if ($arm & 1) {
205 1443         2238 ($x,$y) = ($y+1,$x+1); # transpose and offset
206             }
207 2653 100       4251 if ($arm & 2) {
208 1306         1999 ($x,$y) = (-$y,$x+1); # rotate +90 and offset
209             }
210 2653 100       4102 if ($arm & 4) {
211 1046         1364 $x = -1 - $x; # rotate 180 and offset
212 1046         1284 $y = 1 - $y;
213             }
214              
215             # ### rotated return: "$x,$y"
216 2653         5454 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 480 my ($self, $x, $y) = @_;
260             ### AlternatePaperMidpoint xy_to_n(): "$x, $y"
261              
262 44         83 $x = round_nearest($x);
263 44         82 $y = round_nearest($y);
264              
265 44 50       78 if (is_infinite($x)) {
266 0         0 return $x; # infinity
267             }
268 44 50       96 if (is_infinite($y)) {
269 0         0 return $y; # infinity
270             }
271              
272             # arm in various octants, rotate/transpose to first
273 44         61 my $arm;
274 44 100       95 if ($y >= ($x>=0?0:2)) { # Y>=0 when X positive, Y>=2 when X negative
    100          
275 37         43 $arm = 0;
276             } else {
277             # lower arms 4,5,6,7 ...
278 7         8 $arm = 4;
279 7         10 $x = -1 - $x; # rotate 180, offset
280 7         10 $y = 1 - $y;
281             }
282 44 100       90 if ($x < ($y>0?1:0)) {
    100          
283             ### second quad arms 2,3 ...
284 7         13 ($x,$y) = ($y-1,-$x); # rotate -90, offset
285 7         7 $arm += 2;
286             }
287 44 100       90 if ($y > $x-($x%2)) {
288             ### above diagonal, arm 1 ...
289 14         33 ($x,$y) = ($y-1,$x-1); # offset and transpose
290 14         17 $arm++;
291             }
292             ### assert: $x >= 0
293             ### assert: $y >= 0
294             ### assert: $y <= $x - ($x%2)
295              
296 44 50       87 if ($arm >= $self->{'arms'}) {
297 0         0 return undef;
298             }
299              
300 44         88 my ($len, $level) = round_down_pow ($x, 2);
301 44 50       85 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         66 my $n = ($x * 0 * $y); # inherit bignum 0
324 44         58 my $rev = 0;
325              
326 44         56 $len *= 2;
327 44         77 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 200         245 my $digit;
336 200 100       288 if ($x < $len) {
337             ### diagonal: $x+$y+($x%2), $len
338 119 100       204 if ($x+$y+($x%2) < $len) {
339             ### part 0 ...
340 63         76 $digit = 0;
341             } else {
342             ### part 1 ...
343 56         88 ($x,$y) = ($y,$len-1-$x); # shift, rotate -90
344 56         72 $rev ^= 3;
345 56         99 $digit = 2; # becoming digit=1 with reverse
346             }
347             } else {
348 81         95 $x -= $len;
349             ### 2,3 ycmp: $y, $x-($x%2)
350 81 100       119 if ($y <= $x-($x%2)) {
351             ### part 2 ...
352 35         40 $digit = 2;
353             } else {
354             ### part 3 ...
355 46         85 ($x,$y) = ($len-1-$y,$x); # shift, rotate +90
356 46         59 $rev ^= 3;
357 46         52 $digit = 0; # becoming digit=3 with reverse
358             }
359             }
360             ### $digit
361              
362 200         258 $digit ^= $rev; # $digit = 3-$digit if reverse
363             ### reversed digit: $digit
364              
365 200         278 $n *= 4;
366 200         268 $n += $digit;
367 200         336 $len /= 2;
368             }
369             ### final: "xy=$x,$y rev=$rev"
370              
371             ### assert: $x == 0
372             ### assert: $y == 0
373              
374 44         102 return $n*$self->{'arms'} + $arm;
375             }
376              
377              
378             # not exact
379             sub rect_to_n_range {
380 40     40 1 2686 my ($self, $x1,$y1, $x2,$y2) = @_;
381             ### AlternatePaperMidpoint rect_to_n_range(): "$x1,$y1 $x2,$y2 arms=$self->{'arms'}"
382              
383 40         94 $x1 = round_nearest($x1);
384 40         68 $x2 = round_nearest($x2);
385 40         73 $y1 = round_nearest($y1);
386 40         66 $y2 = round_nearest($y2);
387              
388 40 50       78 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
389 40 50       67 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
390              
391 40         67 my $arms = $self->{'arms'};
392 40 50 66     247 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       177 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         95 return (0, 2*$arms*$len*$len-1);
405             }
406              
407             #------------------------------------------------------------------------------
408             # levels
409              
410 1     1   7 use Math::PlanePath::DragonMidpoint;
  1         2  
  1         61  
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__