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 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   1293 use 5.004;
  2         6  
33 2     2   8 use strict;
  2         3  
  2         51  
34              
35 2     2   10 use vars '$VERSION', '@ISA';
  2         3  
  2         106  
36             $VERSION = 127;
37 2     2   645 use Math::PlanePath;
  2         4  
  2         46  
38 2     2   453 use Math::PlanePath::Base::NSEW;
  2         3  
  2         88  
39             @ISA = ('Math::PlanePath::Base::NSEW',
40             'Math::PlanePath');
41              
42             use Math::PlanePath::Base::Generic
43 2         87 'is_infinite',
44 2     2   11 'round_nearest';
  2         3  
45             use Math::PlanePath::Base::Digits
46 2         101 'round_down_pow',
47             'bit_split_lowtohigh',
48             'digit_split_lowtohigh',
49 2     2   493 'digit_join_lowtohigh';
  2         5  
50              
51             # uncomment this to run the ### lines
52             #use Smart::Comments;
53              
54              
55              
56              
57 2     2   11 use constant n_start => 0;
  2         4  
  2         80  
58 2     2   9 use constant class_x_negative => 0;
  2         4  
  2         115  
59 2     2   12 use constant y_negative_at_n => 4;
  2         3  
  2         106  
60             *xy_is_visited = \&Math::PlanePath::Base::Generic::_xy_is_visited_x_positive;
61 2     2   11 use constant _UNDOCUMENTED__dxdy_list_at_n => 4;
  2         3  
  2         2443  
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 21162 my ($self, $n) = @_;
143             ### BetaOmega n_to_xy(): $n
144             ### hex: sprintf "%#X", $n
145              
146 2185 50       3441 if ($n < 0) { return; }
  0         0  
147 2185 50       3523 if (is_infinite($n)) { return ($n,$n); }
  0         0  
148              
149 2185         3469 my $int = int($n);
150 2185         2505 $n -= $int; # remaining fraction, preserve possible BigFloat/BigRat
151              
152 2185         2376 my $zero = $int * 0; # inherit bignum
153 2185         3443 my @ndigits = digit_split_lowtohigh($int,4);
154             ### ndigits: join(', ',@ndigits)." count ".scalar(@ndigits)
155              
156 2185 100       3408 my $state = ($#ndigits & 1 ? 28 : 0);
157 2185 100       2981 my $dirstate = ($#ndigits & 1 ? 0 : 28); # default if all $ndigit==3
158 2185         2618 my @xbits;
159             my @ybits;
160              
161 2185         3222 foreach my $i (reverse 0 .. $#ndigits) {
162 10154         10939 my $ndigit = $ndigits[$i]; # high to low
163 10154         10348 $state += $ndigit;
164 10154 100       13470 if ($ndigit != 3) {
165 7485         8017 $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 10154         11536 $xbits[$i] = $digit_to_x[$state];
176 10154         11296 $ybits[$i] = $digit_to_y[$state];
177 10154         11756 $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         4979 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   1525 my ($y) = @_;
199 53         58 my $pos;
200 53 100       84 if ($pos = ($y >= 0)) {
201             # eg. 1 becomes 3, or 5 becomes 15, 2^k-1
202 26         30 $y = 3 * $y;
203             } else {
204             # eg. -2 becomes 7, or -10 becomes 31, 2^k-1
205 27         38 $y = 1 - 3*$y;
206             }
207 53         107 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       116 if (($level & 1) == $pos) {
212 41         42 $level--;
213 41         49 $len /= 2;
214             }
215              
216 53         87 return ($len, $level);
217             }
218              
219             sub xy_to_n {
220 16     16 1 1154 my ($self, $x, $y) = @_;
221             ### BetaOmega xy_to_n(): "$x, $y"
222              
223 16         53 $x = round_nearest ($x);
224 16 50       29 if ($x < 0) {
225 0         0 return undef;
226             }
227 16 50       26 if (is_infinite($x)) {
228 0         0 return $x;
229             }
230 16         37 my @xbits = bit_split_lowtohigh($x);
231              
232 16         28 $y = round_nearest ($y);
233 16         21 my $zero = ($x * 0 * $y);
234 16         24 my ($len, $level) = _y_round_down_len_level ($y);
235             ### y: "len=$len level=$level"
236              
237 16 50       29 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       40 $y += (($level&1 ? 4 : 2) * $len - 2) / 3;
246             ### offset y to: $y
247 16 50       30 if (is_infinite($y)) {
248 0         0 return $y;
249             }
250 16         32 my @ybits = bit_split_lowtohigh($y);
251 16 100       30 my $state = ($level & 1 ? 28 : 0);
252              
253 16         19 my @ndigits;
254 16         27 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     133 my $ndigit = $xy_to_digit[$state + 2*($xbits[$i]||0) + ($ybits[$i]||0)];
      100        
258 40         50 $ndigits[$i] = $ndigit;
259 40         60 $state = $next_state[$state+$ndigit];
260             }
261              
262 16         35 return digit_join_lowtohigh(\@ndigits, 4, $zero);
263             }
264              
265             # exact
266             sub rect_to_n_range {
267 24     24 1 1797 my ($self, $x1,$y1, $x2,$y2) = @_;
268             ### BetaOmega rect_to_n_range(): "$x1,$y1, $x2,$y2"
269              
270 24         47 $x1 = round_nearest ($x1);
271 24         38 $x2 = round_nearest ($x2);
272 24 50       132 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
273              
274 24 50       38 if ($x2 < 0) {
275 0         0 return (1, 0);
276             }
277              
278 24         38 $y1 = round_nearest ($y1);
279 24         35 $y2 = round_nearest ($y2);
280 24 100       41 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
281              
282 24         45 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       55 foreach my $y (($y2 > 0 ? ($y2) : ()),
    100          
288             ($y1 < 0 ? ($y1) : ())) {
289 20         31 my ($ylen, $ylevel) = _y_round_down_len_level ($y);
290             ### y len/level: "$ylen $ylevel"
291 20 100       40 if ($ylevel > $level) {
292 12         14 $level = $ylevel;
293 12         19 $len = $ylen;
294             }
295             }
296 24 50       65 if (is_infinite($len)) {
297 0         0 return (0, $len);
298             }
299              
300 24         37 my $n_min = my $n_max = 0;
301 24         51 my $y_min = my $y_max = - (4**int(($level+1)/2) - 1) * 2 / 3;
302 24         29 my $x_min = my $x_max = 0;
303 24 100       39 my $min_state = my $max_state = ($level & 1 ? 28 : 0);
304             ### $x_min
305             ### $y_min
306              
307 24         40 while ($level >= 0) {
308             ### $level
309             ### $len
310             {
311 52         57 my $x_cmp = $x_min + $len;
312 52         63 my $y_cmp = $y_min + $len;
313 52 100       123 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         68 $n_min = 4*$n_min + $digit;
330 52         59 $min_state += $digit;
331 52 50       66 if ($digit_to_x[$min_state]) { $x_min += $len; }
  0         0  
332 52         66 $y_min += $len * $digit_to_y[$min_state];
333 52         62 $min_state = $next_state[$min_state];
334             }
335             {
336 52         54 my $x_cmp = $x_max + $len;
  52         51  
  52         57  
337 52         58 my $y_cmp = $y_max + $len;
338 52 100       110 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         57 $n_max = 4*$n_max + $digit;
358 52         52 $max_state += $digit;
359 52 100       75 if ($digit_to_x[$max_state]) { $x_max += $len; }
  13         14  
360 52         75 $y_max += $len * $digit_to_y[$max_state];
361 52         60 $max_state = $next_state[$max_state];
362             }
363              
364 52         63 $len = int($len/2);
365 52         81 $level--;
366             }
367              
368 24         48 return ($n_min, $n_max);
369             }
370              
371             #------------------------------------------------------------------------------
372             # levels
373              
374 2     2   1120 use Math::PlanePath::HilbertCurve;
  2         4  
  2         165  
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__