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 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   1522 use 5.004;
  2         7  
33 2     2   10 use strict;
  2         3  
  2         45  
34              
35 2     2   9 use vars '$VERSION', '@ISA';
  2         2  
  2         116  
36             $VERSION = 128;
37 2     2   796 use Math::PlanePath;
  2         4  
  2         48  
38 2     2   542 use Math::PlanePath::Base::NSEW;
  2         4  
  2         87  
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         4  
45             use Math::PlanePath::Base::Digits
46 2         115 'round_down_pow',
47             'bit_split_lowtohigh',
48             'digit_split_lowtohigh',
49 2     2   603 'digit_join_lowtohigh';
  2         4  
50              
51             # uncomment this to run the ### lines
52             #use Smart::Comments;
53              
54              
55              
56              
57 2     2   13 use constant n_start => 0;
  2         3  
  2         87  
58 2     2   10 use constant class_x_negative => 0;
  2         10  
  2         78  
59 2     2   10 use constant y_negative_at_n => 4;
  2         4  
  2         125  
60             *xy_is_visited = \&Math::PlanePath::Base::Generic::_xy_is_visited_x_positive;
61 2     2   12 use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 4;
  2         31  
  2         2698  
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 24383 my ($self, $n) = @_;
143             ### BetaOmega n_to_xy(): $n
144             ### hex: sprintf "%#X", $n
145              
146 2185 50       4044 if ($n < 0) { return; }
  0         0  
147 2185 50       4156 if (is_infinite($n)) { return ($n,$n); }
  0         0  
148              
149 2185         3826 my $int = int($n);
150 2185         3051 $n -= $int; # remaining fraction, preserve possible BigFloat/BigRat
151              
152 2185         2942 my $zero = $int * 0; # inherit bignum
153 2185         4043 my @ndigits = digit_split_lowtohigh($int,4);
154             ### ndigits: join(', ',@ndigits)." count ".scalar(@ndigits)
155              
156 2185 100       4254 my $state = ($#ndigits & 1 ? 28 : 0);
157 2185 100       3389 my $dirstate = ($#ndigits & 1 ? 0 : 28); # default if all $ndigit==3
158 2185         3220 my @xbits;
159             my @ybits;
160              
161 2185         4208 foreach my $i (reverse 0 .. $#ndigits) {
162 10111         12730 my $ndigit = $ndigits[$i]; # high to low
163 10111         12738 $state += $ndigit;
164 10111 100       16191 if ($ndigit != 3) {
165 7462         9626 $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 10111         14012 $xbits[$i] = $digit_to_x[$state];
176 10111         13383 $ybits[$i] = $digit_to_y[$state];
177 10111         14542 $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         5888 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   1790 my ($y) = @_;
199 53         65 my $pos;
200 53 100       110 if ($pos = ($y >= 0)) {
201             # eg. 1 becomes 3, or 5 becomes 15, 2^k-1
202 26         41 $y = 3 * $y;
203             } else {
204             # eg. -2 becomes 7, or -10 becomes 31, 2^k-1
205 27         44 $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       131 if (($level & 1) == $pos) {
212 41         53 $level--;
213 41         53 $len /= 2;
214             }
215              
216 53         104 return ($len, $level);
217             }
218              
219             sub xy_to_n {
220 16     16 1 1043 my ($self, $x, $y) = @_;
221             ### BetaOmega xy_to_n(): "$x, $y"
222              
223 16         41 $x = round_nearest ($x);
224 16 50       38 if ($x < 0) {
225 0         0 return undef;
226             }
227 16 50       33 if (is_infinite($x)) {
228 0         0 return $x;
229             }
230 16         44 my @xbits = bit_split_lowtohigh($x);
231              
232 16         34 $y = round_nearest ($y);
233 16         25 my $zero = ($x * 0 * $y);
234 16         42 my ($len, $level) = _y_round_down_len_level ($y);
235             ### y: "len=$len level=$level"
236              
237 16 50       35 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       68 $y += (($level&1 ? 4 : 2) * $len - 2) / 3;
246             ### offset y to: $y
247 16 50       38 if (is_infinite($y)) {
248 0         0 return $y;
249             }
250 16         40 my @ybits = bit_split_lowtohigh($y);
251 16 100       35 my $state = ($level & 1 ? 28 : 0);
252              
253 16         22 my @ndigits;
254 16         34 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     131 my $ndigit = $xy_to_digit[$state + 2*($xbits[$i]||0) + ($ybits[$i]||0)];
      100        
258 40         65 $ndigits[$i] = $ndigit;
259 40         67 $state = $next_state[$state+$ndigit];
260             }
261              
262 16         37 return digit_join_lowtohigh(\@ndigits, 4, $zero);
263             }
264              
265             # exact
266             sub rect_to_n_range {
267 24     24 1 1748 my ($self, $x1,$y1, $x2,$y2) = @_;
268             ### BetaOmega rect_to_n_range(): "$x1,$y1, $x2,$y2"
269              
270 24         58 $x1 = round_nearest ($x1);
271 24         45 $x2 = round_nearest ($x2);
272 24 50       50 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
273              
274 24 50       41 if ($x2 < 0) {
275 0         0 return (1, 0);
276             }
277              
278 24         43 $y1 = round_nearest ($y1);
279 24         48 $y2 = round_nearest ($y2);
280 24 100       49 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
281              
282 24         54 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       63 foreach my $y (($y2 > 0 ? ($y2) : ()),
    100          
288             ($y1 < 0 ? ($y1) : ())) {
289 20         34 my ($ylen, $ylevel) = _y_round_down_len_level ($y);
290             ### y len/level: "$ylen $ylevel"
291 20 100       41 if ($ylevel > $level) {
292 12         17 $level = $ylevel;
293 12         19 $len = $ylen;
294             }
295             }
296 24 50       51 if (is_infinite($len)) {
297 0         0 return (0, $len);
298             }
299              
300 24         43 my $n_min = my $n_max = 0;
301 24         59 my $y_min = my $y_max = - (4**int(($level+1)/2) - 1) * 2 / 3;
302 24         35 my $x_min = my $x_max = 0;
303 24 100       42 my $min_state = my $max_state = ($level & 1 ? 28 : 0);
304             ### $x_min
305             ### $y_min
306              
307 24         64 while ($level >= 0) {
308             ### $level
309             ### $len
310             {
311 52         79 my $x_cmp = $x_min + $len;
312 52         67 my $y_cmp = $y_min + $len;
313 52 100       170 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         77 $n_min = 4*$n_min + $digit;
330 52         65 $min_state += $digit;
331 52 50       89 if ($digit_to_x[$min_state]) { $x_min += $len; }
  0         0  
332 52         74 $y_min += $len * $digit_to_y[$min_state];
333 52         72 $min_state = $next_state[$min_state];
334             }
335             {
336 52         64 my $x_cmp = $x_max + $len;
  52         60  
  52         93  
337 52         70 my $y_cmp = $y_max + $len;
338 52 100       129 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         68 $n_max = 4*$n_max + $digit;
358 52         69 $max_state += $digit;
359 52 100       117 if ($digit_to_x[$max_state]) { $x_max += $len; }
  13         15  
360 52         84 $y_max += $len * $digit_to_y[$max_state];
361 52         66 $max_state = $next_state[$max_state];
362             }
363              
364 52         75 $len = int($len/2);
365 52         89 $level--;
366             }
367              
368 24         63 return ($n_min, $n_max);
369             }
370              
371             #------------------------------------------------------------------------------
372             # levels
373              
374 2     2   1216 use Math::PlanePath::HilbertCurve;
  2         5  
  2         188  
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__