File Coverage

blib/lib/Math/PlanePath/BetaOmega.pm
Criterion Covered Total %
statement 125 135 92.5
branch 50 62 80.6
condition 4 4 100.0
subroutine 16 16 100.0
pod 3 3 100.0
total 198 220 90.0


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=BetaOmega --lines --scale=20
20             #
21             # math-image --path=BetaOmega --all --output=numbers_dash
22              
23             # http://www.upb.de/pc2/papers/files/pdfps399main.toappear.ps # gone
24             # http://www.uni-paderborn.de/pc2/papers/files/pdfps399main.toappear.ps
25             # http://wwwcs.upb.de/pc2/papers/files/399.ps # gone
26             #
27             # copy ?
28             # http://www.cs.uleth.ca/~wismath/cccg/papers/27l.ps
29              
30              
31             package Math::PlanePath::BetaOmega;
32 2     2   1593 use 5.004;
  2         7  
33 2     2   10 use strict;
  2         3  
  2         49  
34              
35 2     2   10 use vars '$VERSION', '@ISA';
  2         3  
  2         122  
36             $VERSION = 129;
37 2     2   817 use Math::PlanePath;
  2         5  
  2         52  
38 2     2   570 use Math::PlanePath::Base::NSEW;
  2         4  
  2         93  
39             @ISA = ('Math::PlanePath::Base::NSEW',
40             'Math::PlanePath');
41              
42             use Math::PlanePath::Base::Generic
43 2         97 'is_infinite',
44 2     2   21 'round_nearest';
  2         3  
45             use Math::PlanePath::Base::Digits
46 2         127 'round_down_pow',
47             'bit_split_lowtohigh',
48             'digit_split_lowtohigh',
49 2     2   619 'digit_join_lowtohigh';
  2         4  
50              
51             # uncomment this to run the ### lines
52             #use Smart::Comments;
53              
54              
55              
56              
57 2     2   14 use constant n_start => 0;
  2         3  
  2         97  
58 2     2   11 use constant class_x_negative => 0;
  2         12  
  2         87  
59 2     2   11 use constant y_negative_at_n => 4;
  2         4  
  2         144  
60             *xy_is_visited = \&Math::PlanePath::Base::Generic::_xy_is_visited_x_positive;
61 2     2   13 use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 4;
  2         36  
  2         2943  
62              
63              
64             #------------------------------------------------------------------------------
65              
66             # tables generated by tools/beta-omega-table.pl
67             #
68             my @next_state = (28, 8,36,88, 8,28,32,76, 4,16,44,64, 16, 4,40,84,
69             12,24,52,72, 24,12,48,92, 20, 0,60,80, 0,20,56,68,
70             68, 4,40,60, 64, 0,60,40, 76,12,48,36, 72, 8,36,48,
71             84,20,56,44, 80,16,44,56, 92,28,32,52, 88,24,52,32,
72             28, 8,36,48, 8,28,32,52, 4,16,44,56, 16, 4,40,60,
73             12,24,52,32, 24,12,48,36, 20, 0,60,40, 0,20,56,44);
74             my @digit_to_x = (0,0,1,1, 0,1,1,0, 1,0,0,1, 1,1,0,0,
75             1,1,0,0, 1,0,0,1, 0,1,1,0, 0,0,1,1,
76             1,1,0,0, 0,1,1,0, 1,0,0,1, 0,0,1,1,
77             0,0,1,1, 1,0,0,1, 0,1,1,0, 1,1,0,0,
78             0,0,1,1, 0,1,1,0, 1,0,0,1, 1,1,0,0,
79             1,1,0,0, 1,0,0,1, 0,1,1,0, 0,0,1,1);
80             my @digit_to_y = (0,1,1,0, 0,0,1,1, 0,0,1,1, 0,1,1,0,
81             1,0,0,1, 1,1,0,0, 1,1,0,0, 1,0,0,1,
82             0,1,1,0, 1,1,0,0, 1,1,0,0, 0,1,1,0,
83             1,0,0,1, 0,0,1,1, 0,0,1,1, 1,0,0,1,
84             0,1,1,0, 0,0,1,1, 0,0,1,1, 0,1,1,0,
85             1,0,0,1, 1,1,0,0, 1,1,0,0, 1,0,0,1);
86             my @xy_to_digit = (0,1,3,2, 0,3,1,2, 1,2,0,3, 3,2,0,1,
87             2,3,1,0, 2,1,3,0, 3,0,2,1, 1,0,2,3,
88             3,2,0,1, 3,0,2,1, 2,1,3,0, 0,1,3,2,
89             1,0,2,3, 1,2,0,3, 0,3,1,2, 2,3,1,0,
90             0,1,3,2, 0,3,1,2, 1,2,0,3, 3,2,0,1,
91             2,3,1,0, 2,1,3,0, 3,0,2,1, 1,0,2,3);
92             my @min_digit = (0,0,3,0, 0,2,1,1, 2,undef,undef,undef,
93             0,0,1,0, 0,1,3,2, 2,undef,undef,undef,
94             1,0,0,1, 0,0,2,2, 3,undef,undef,undef,
95             3,0,0,2, 0,0,2,1, 1,undef,undef,undef,
96             2,1,1,2, 0,0,3,0, 0,undef,undef,undef,
97             2,2,3,1, 0,0,1,0, 0,undef,undef,undef,
98             3,2,2,0, 0,1,0,0, 1,undef,undef,undef,
99             1,1,2,0, 0,2,0,0, 3,undef,undef,undef,
100             3,0,0,2, 0,0,2,1, 1,undef,undef,undef,
101             3,2,2,0, 0,1,0,0, 1,undef,undef,undef,
102             2,2,3,1, 0,0,1,0, 0,undef,undef,undef,
103             0,0,3,0, 0,2,1,1, 2,undef,undef,undef,
104             1,1,2,0, 0,2,0,0, 3,undef,undef,undef,
105             1,0,0,1, 0,0,2,2, 3,undef,undef,undef,
106             0,0,1,0, 0,1,3,2, 2,undef,undef,undef,
107             2,1,1,2, 0,0,3,0, 0,undef,undef,undef,
108             0,0,3,0, 0,2,1,1, 2,undef,undef,undef,
109             0,0,1,0, 0,1,3,2, 2,undef,undef,undef,
110             1,0,0,1, 0,0,2,2, 3,undef,undef,undef,
111             3,0,0,2, 0,0,2,1, 1,undef,undef,undef,
112             2,1,1,2, 0,0,3,0, 0,undef,undef,undef,
113             2,2,3,1, 0,0,1,0, 0,undef,undef,undef,
114             3,2,2,0, 0,1,0,0, 1,undef,undef,undef,
115             1,1,2,0, 0,2,0,0, 3);
116             my @max_digit = (0,3,3,1, 3,3,1,2, 2,undef,undef,undef,
117             0,1,1,3, 3,2,3,3, 2,undef,undef,undef,
118             1,1,0,2, 3,3,2,3, 3,undef,undef,undef,
119             3,3,0,3, 3,1,2,2, 1,undef,undef,undef,
120             2,2,1,3, 3,1,3,3, 0,undef,undef,undef,
121             2,3,3,2, 3,3,1,1, 0,undef,undef,undef,
122             3,3,2,3, 3,2,0,1, 1,undef,undef,undef,
123             1,2,2,1, 3,3,0,3, 3,undef,undef,undef,
124             3,3,0,3, 3,1,2,2, 1,undef,undef,undef,
125             3,3,2,3, 3,2,0,1, 1,undef,undef,undef,
126             2,3,3,2, 3,3,1,1, 0,undef,undef,undef,
127             0,3,3,1, 3,3,1,2, 2,undef,undef,undef,
128             1,2,2,1, 3,3,0,3, 3,undef,undef,undef,
129             1,1,0,2, 3,3,2,3, 3,undef,undef,undef,
130             0,1,1,3, 3,2,3,3, 2,undef,undef,undef,
131             2,2,1,3, 3,1,3,3, 0,undef,undef,undef,
132             0,3,3,1, 3,3,1,2, 2,undef,undef,undef,
133             0,1,1,3, 3,2,3,3, 2,undef,undef,undef,
134             1,1,0,2, 3,3,2,3, 3,undef,undef,undef,
135             3,3,0,3, 3,1,2,2, 1,undef,undef,undef,
136             2,2,1,3, 3,1,3,3, 0,undef,undef,undef,
137             2,3,3,2, 3,3,1,1, 0,undef,undef,undef,
138             3,3,2,3, 3,2,0,1, 1,undef,undef,undef,
139             1,2,2,1, 3,3,0,3, 3);
140              
141             sub n_to_xy {
142 2185     2185 1 26288 my ($self, $n) = @_;
143             ### BetaOmega n_to_xy(): $n
144             ### hex: sprintf "%#X", $n
145              
146 2185 50       4267 if ($n < 0) { return; }
  0         0  
147 2185 50       4066 if (is_infinite($n)) { return ($n,$n); }
  0         0  
148              
149 2185         3795 my $int = int($n);
150 2185         3046 $n -= $int; # remaining fraction, preserve possible BigFloat/BigRat
151              
152 2185         2915 my $zero = $int * 0; # inherit bignum
153 2185         4280 my @ndigits = digit_split_lowtohigh($int,4);
154             ### ndigits: join(', ',@ndigits)." count ".scalar(@ndigits)
155              
156 2185 100       4268 my $state = ($#ndigits & 1 ? 28 : 0);
157 2185 100       3560 my $dirstate = ($#ndigits & 1 ? 0 : 28); # default if all $ndigit==3
158 2185         3247 my @xbits;
159             my @ybits;
160              
161 2185         3937 foreach my $i (reverse 0 .. $#ndigits) {
162 10181         13629 my $ndigit = $ndigits[$i]; # high to low
163 10181         12482 $state += $ndigit;
164 10181 100       16301 if ($ndigit != 3) {
165 7477         9476 $dirstate = $state; # lowest non-3 digit
166             }
167              
168             ### $ndigit
169             ### $state
170             ### $dirstate
171             ### digit_to_x: $digit_to_x[$state]
172             ### digit_to_y: $digit_to_y[$state]
173             ### next_state: $next_state[$state]
174              
175 10181         13972 $xbits[$i] = $digit_to_x[$state];
176 10181         13665 $ybits[$i] = $digit_to_y[$state];
177 10181         15797 $state = $next_state[$state];
178             }
179              
180             ### $dirstate
181             ### frac: $n
182             ### Ymin: - (((4+$zero)**int($#ndigits/2) - 1) * 2 / 3)
183              
184             # with $n fractional part
185 2185         6281 return ($n * ($digit_to_x[$dirstate+1] - $digit_to_x[$dirstate])
186             + digit_join_lowtohigh(\@xbits, 2, $zero),
187              
188             $n * ($digit_to_y[$dirstate+1] - $digit_to_y[$dirstate])
189             + (digit_join_lowtohigh(\@ybits, 2, $zero)
190              
191             # Ymin = - (4^floor(level/2) - 1) * 2 / 3
192             - (((4+$zero)**int(scalar(@ndigits)/2) - 1) * 2 / 3)));
193             }
194              
195              
196             # ($len,$level) rounded down for $y ...
197             sub _y_round_down_len_level {
198 53     53   1777 my ($y) = @_;
199 53         73 my $pos;
200 53 100       118 if ($pos = ($y >= 0)) {
201             # eg. 1 becomes 3, or 5 becomes 15, 2^k-1
202 26         38 $y = 3 * $y;
203             } else {
204             # eg. -2 becomes 7, or -10 becomes 31, 2^k-1
205 27         45 $y = 1 - 3*$y;
206             }
207 53         109 my ($len, $level) = round_down_pow($y,2);
208              
209             # Make positive y give even level, and negative y give odd level.
210             # If positive and odd then reduce, or if negative and even then reduce.
211 53 100       137 if (($level & 1) == $pos) {
212 41         53 $level--;
213 41         56 $len /= 2;
214             }
215              
216 53         121 return ($len, $level);
217             }
218              
219             sub xy_to_n {
220 16     16 1 1414 my ($self, $x, $y) = @_;
221             ### BetaOmega xy_to_n(): "$x, $y"
222              
223 16         54 $x = round_nearest ($x);
224 16 50       35 if ($x < 0) {
225 0         0 return undef;
226             }
227 16 50       35 if (is_infinite($x)) {
228 0         0 return $x;
229             }
230 16         46 my @xbits = bit_split_lowtohigh($x);
231              
232 16         36 $y = round_nearest ($y);
233 16         28 my $zero = ($x * 0 * $y);
234 16         38 my ($len, $level) = _y_round_down_len_level ($y);
235             ### y: "len=$len level=$level"
236              
237 16 50       32 if ($#xbits > $level) {
238             ### increase level to xbits ...
239 0         0 $level = $#xbits;
240 0         0 $len = (2+$zero) ** $level;
241             }
242             ### $len
243             ### $level
244              
245 16 100       45 $y += (($level&1 ? 4 : 2) * $len - 2) / 3;
246             ### offset y to: $y
247 16 50       34 if (is_infinite($y)) {
248 0         0 return $y;
249             }
250 16         36 my @ybits = bit_split_lowtohigh($y);
251 16 100       38 my $state = ($level & 1 ? 28 : 0);
252              
253 16         19 my @ndigits;
254 16         36 foreach my $i (reverse 0 .. $level) { # high to low
255             ### at: "i=$i state=$state xbit=".($xbits[$i]||0)." ybit=".($ybits[$i]||0)
256              
257 40   100     141 my $ndigit = $xy_to_digit[$state + 2*($xbits[$i]||0) + ($ybits[$i]||0)];
      100        
258 40         65 $ndigits[$i] = $ndigit;
259 40         64 $state = $next_state[$state+$ndigit];
260             }
261              
262 16         39 return digit_join_lowtohigh(\@ndigits, 4, $zero);
263             }
264              
265             # exact
266             sub rect_to_n_range {
267 24     24 1 2309 my ($self, $x1,$y1, $x2,$y2) = @_;
268             ### BetaOmega rect_to_n_range(): "$x1,$y1, $x2,$y2"
269              
270 24         62 $x1 = round_nearest ($x1);
271 24         48 $x2 = round_nearest ($x2);
272 24 50       47 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
273              
274 24 50       46 if ($x2 < 0) {
275 0         0 return (1, 0);
276             }
277              
278 24         37 $y1 = round_nearest ($y1);
279 24         45 $y2 = round_nearest ($y2);
280 24 100       55 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
281              
282 24         57 my ($len, $level) = round_down_pow ($x2, 2);
283             ### x len/level: "$len $level"
284              
285             # If y1/y2 both positive or both negative then only look at the bigger of
286             # the two. If y1 negative and y2 positive then consider both.
287 24 100       66 foreach my $y (($y2 > 0 ? ($y2) : ()),
    100          
288             ($y1 < 0 ? ($y1) : ())) {
289 20         39 my ($ylen, $ylevel) = _y_round_down_len_level ($y);
290             ### y len/level: "$ylen $ylevel"
291 20 100       46 if ($ylevel > $level) {
292 12         16 $level = $ylevel;
293 12         21 $len = $ylen;
294             }
295             }
296 24 50       48 if (is_infinite($len)) {
297 0         0 return (0, $len);
298             }
299              
300 24         45 my $n_min = my $n_max = 0;
301 24         63 my $y_min = my $y_max = - (4**int(($level+1)/2) - 1) * 2 / 3;
302 24         32 my $x_min = my $x_max = 0;
303 24 100       45 my $min_state = my $max_state = ($level & 1 ? 28 : 0);
304             ### $x_min
305             ### $y_min
306              
307 24         48 while ($level >= 0) {
308             ### $level
309             ### $len
310             {
311 52         67 my $x_cmp = $x_min + $len;
312 52         77 my $y_cmp = $y_min + $len;
313 52 100       141 my $digit = $min_digit[3*$min_state
    50          
    100          
    100          
314             + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0)
315             + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)];
316              
317             # my $xr = ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0);
318             # my $yr = ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0);
319             # my $key = 3*$min_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0);
320             # ### min at: "min_state=$min_state $x_min,$y_min cmp $x_cmp,$y_cmp"
321             # ### min_state: state_string($min_state)
322             # ### $xr
323             # ### $yr
324             # ### $key
325             # ### min digit: $digit
326             # ### min key: $key
327             # ### y offset: $digit_to_y[$max_state+$digit]
328              
329 52         72 $n_min = 4*$n_min + $digit;
330 52         64 $min_state += $digit;
331 52 50       87 if ($digit_to_x[$min_state]) { $x_min += $len; }
  0         0  
332 52         81 $y_min += $len * $digit_to_y[$min_state];
333 52         72 $min_state = $next_state[$min_state];
334             }
335             {
336 52         62 my $x_cmp = $x_max + $len;
  52         62  
  52         90  
337 52         78 my $y_cmp = $y_max + $len;
338 52 100       136 my $digit = $max_digit[3*$max_state
    50          
    100          
    100          
339             + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0)
340             + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)];
341              
342             # my $xr = ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0);
343             # my $yr = ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0);
344             # my $key = 3*$min_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0);
345             # ### max at: "max_state=$max_state $x_max,$y_max cmp $x_cmp,$y_cmp"
346             # ### $x_cmp
347             # ### $y_cmp
348             # ### $xr
349             # ### $yr
350             # ### $key
351             # ### max digit: $digit
352             # ### x offset: $digit_to_x[$max_state+$digit]
353             # ### y offset: $digit_to_y[$max_state+$digit]
354             # ### y digit offset: $digit_to_y[$max_state+$digit]
355             # ### y min shift part: - ($level&1)
356              
357 52         71 $n_max = 4*$n_max + $digit;
358 52         63 $max_state += $digit;
359 52 100       89 if ($digit_to_x[$max_state]) { $x_max += $len; }
  13         16  
360 52         74 $y_max += $len * $digit_to_y[$max_state];
361 52         75 $max_state = $next_state[$max_state];
362             }
363              
364 52         76 $len = int($len/2);
365 52         95 $level--;
366             }
367              
368 24         57 return ($n_min, $n_max);
369             }
370              
371             #------------------------------------------------------------------------------
372             # levels
373              
374 2     2   1337 use Math::PlanePath::HilbertCurve;
  2         5  
  2         205  
375             *level_to_n_range = \&Math::PlanePath::HilbertCurve::level_to_n_range;
376             *n_to_level = \&Math::PlanePath::HilbertCurve::n_to_level;
377              
378             #------------------------------------------------------------------------------
379             1;
380             __END__