File Coverage

blib/lib/Math/PlanePath/PeanoCurve.pm
Criterion Covered Total %
statement 156 192 81.2
branch 30 62 48.3
condition 7 18 38.8
subroutine 17 24 70.8
pod 7 7 100.0
total 217 303 71.6


line stmt bran cond sub pod time code
1             # Copyright 2010, 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             # cf
20             #
21             # http://www.cut-the-knot.org/Curriculum/Geometry/PeanoComplete.shtml
22             # Java applet, directions in 9 sub-parts
23             #
24             # math-image --path=PeanoCurve,radix=5 --all --output=numbers
25             # math-image --path=PeanoCurve,radix=5 --lines
26             #
27             # T = 0.a1 a2 ...
28             # X = 0.b1 b2 ...
29             # Y = 0.c1 c2 ...
30             #
31             # b1=a1
32             # c1 = a2 comp(a1)
33             # b2 = a3 comp(a2)
34             # c2 = a4 comp(a1+a3)
35             #
36             # bn = a[2n-1] comp a2+a4+...+a[2n-2]
37             # cn = a[2n] comp a1+a3+...+a[2n-1]
38             #
39             # Brouwer(?) no continuous one-to-one between R and RxR, so line and plane
40             # are distinguished.
41             #
42              
43              
44             package Math::PlanePath::PeanoCurve;
45 4     4   4014 use 5.004;
  4         20  
46 4     4   20 use strict;
  4         9  
  4         185  
47             #use List::Util 'max';
48             *max = \&Math::PlanePath::_max;
49              
50 4     4   23 use vars '$VERSION', '@ISA';
  4         8  
  4         260  
51             $VERSION = 127;
52 4     4   1409 use Math::PlanePath;
  4         10  
  4         172  
53             @ISA = ('Math::PlanePath');
54              
55             use Math::PlanePath::Base::Generic
56 4         215 'is_infinite',
57 4     4   25 'round_nearest';
  4         16  
58             use Math::PlanePath::Base::Digits
59 4         251 'round_down_pow',
60             'digit_split_lowtohigh',
61 4     4   997 'digit_join_lowtohigh';
  4         10  
62 4     4   1358 use Math::PlanePath::Base::NSEW;
  4         11  
  4         106  
63              
64             # uncomment this to run the ### lines
65             # use Smart::Comments;
66              
67              
68 4     4   22 use constant n_start => 0;
  4         16  
  4         196  
69 4     4   23 use constant class_x_negative => 0;
  4         9  
  4         156  
70 4     4   20 use constant class_y_negative => 0;
  4         8  
  4         272  
71             *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1;
72              
73 4         6284 use constant parameter_info_array =>
74             [ { name => 'radix',
75             display => 'Radix',
76             share_key => 'radix_3',
77             type => 'integer',
78             minimum => 2,
79             default => 3,
80             width => 3,
81 4     4   28 } ];
  4         9  
82              
83             # shared by WunderlichSerpentine
84             sub dx_minimum {
85 0     0 1 0 my ($self) = @_;
86 0 0       0 return ($self->{'radix'} % 2
87             ? -1 # odd
88             : undef); # even, unlimited
89             }
90             sub dx_maximum {
91 0     0 1 0 my ($self) = @_;
92 0 0       0 return ($self->{'radix'} % 2
93             ? 1 # odd
94             : undef); # even, unlimited
95             }
96              
97             # shared by WunderlichSerpentine
98             sub _UNDOCUMENTED__dxdy_list {
99 0     0   0 my ($self) = @_;
100 0 0       0 return ($self->{'radix'} % 2
101             ? Math::PlanePath::Base::NSEW->_UNDOCUMENTED__dxdy_list
102             : ()); # even, unlimited
103             }
104             # *--- b^2-1 -- b^2 ---- b^2+b-1 = (b+1)b-1
105             # | |
106             # *-------
107             # |
108             # 0 ----- b
109             #
110             sub _UNDOCUMENTED__dxdy_list_at_n {
111 0     0   0 my ($self) = @_;
112 0         0 return ($self->{'radix'} + 1) * $self->{'radix'} - 1;
113             }
114              
115             # shared by WunderlichSerpentine
116             *dy_minimum = \&dx_minimum;
117             *dy_maximum = \&dx_maximum;
118              
119             *dsumxy_minimum = \&dx_minimum;
120             *dsumxy_maximum = \&dx_maximum;
121              
122             *ddiffxy_minimum = \&dx_minimum;
123             *ddiffxy_maximum = \&dx_maximum;
124              
125             sub dir_maximum_dxdy {
126 0     0 1 0 my ($self) = @_;
127 0 0       0 return ($self->{'radix'} % 2
128             ? (0,-1) # odd, South
129             : (0,0)); # even, supremum
130             }
131              
132             sub _UNDOCUMENTED__turn_any_left_at_n {
133 0     0   0 my ($self) = @_;
134 0         0 return $self->{'radix'} - 1;
135             }
136             sub _UNDOCUMENTED__turn_any_right_at_n {
137 0     0   0 my ($self) = @_;
138             return ($self->{'radix'} == 2 ? 5
139 0 0       0 : 2*$self->{'radix'} - 1);
140             }
141              
142              
143             #------------------------------------------------------------------------------
144              
145             sub new {
146 7     7 1 2467 my $self = shift->SUPER::new(@_);
147              
148 7 100 66     51 if (! $self->{'radix'} || $self->{'radix'} < 2) {
149 6         17 $self->{'radix'} = 3;
150             }
151 7         18 return $self;
152             }
153              
154             sub n_to_xy {
155 5     5 1 6689 my ($self, $n) = @_;
156             ### PeanoCurve n_to_xy(): $n
157 5 50       20 if ($n < 0) { # negative
158 0         0 return;
159             }
160 5 50       810 if (is_infinite($n)) {
161 0         0 return ($n,$n);
162             }
163              
164             {
165             # ENHANCE-ME: for odd radix the ends join and the direction can be had
166             # without a full N+1 calculation
167 5         3148 my $int = int($n);
  5         14  
168             ### $int
169             ### $n
170 5 100       459 if ($n != $int) {
171 1         296 my ($x1,$y1) = $self->n_to_xy($int);
172 1         152 my ($x2,$y2) = $self->n_to_xy($int+1);
173 1         225 my $frac = $n - $int; # inherit possible BigFloat
174 1         498 my $dx = $x2-$x1;
175 1         258 my $dy = $y2-$y1;
176 1         243 return ($frac*$dx + $x1, $frac*$dy + $y1);
177             }
178 4         318 $n = $int; # BigFloat int() gives BigInt, use that
179             }
180              
181 4         8 my $radix = $self->{'radix'};
182 4 50       18 my @ndigits = digit_split_lowtohigh($n,$radix)
183             or return (0,0);
184              
185             # high to low style
186             #
187 4         15 my $radix_minus_1 = $radix - 1;
188 4         7 my $xk = 0;
189 4         10 my $yk = 0;
190 4         11 my @ydigits;
191             my @xdigits;
192              
193 4         23 $#ndigits |= 1; # ensure even number of entries
194 4   50     29 $ndigits[-1] ||= 0; # possible 0 as extra high digit
195             ### @ndigits
196              
197 4         25 foreach my $i (reverse 0 .. ($#ndigits >> 1)) {
198             ### $i
199             {
200 390         179006 my $ndigit = pop @ndigits; # high to low
  390         637  
201 390         667 $xk ^= $ndigit;
202 390 100       147523 $ydigits[$i] = ($yk & 1 ? $radix_minus_1-$ndigit : $ndigit);
203             }
204 390 50       286070 @ndigits || last;
205             {
206 390         575 my $ndigit = pop @ndigits;
  390         568  
207 390         713 $yk ^= $ndigit;
208 390 100       199852 $xdigits[$i] = ($xk & 1 ? $radix_minus_1-$ndigit : $ndigit);
209             }
210             }
211              
212             ### @xdigits
213             ### @ydigits
214 4         1487 my $zero = ($n * 0); # inherit bignum 0
215 4         910 return (digit_join_lowtohigh(\@xdigits, $radix, $zero),
216             digit_join_lowtohigh(\@ydigits, $radix, $zero));
217              
218              
219              
220             # low to high style
221             #
222             # my $x = my $y = ($n * 0); # inherit bignum 0
223             # my $power = 1 + $x; # inherit bignum 1
224             #
225             # while (@ndigits) { # N digits low to high
226             # ### $power
227             # {
228             # my $ndigit = shift @ndigits; # low to high
229             # if ($ndigit & 1) {
230             # $y = $power-1 - $y; # 99..99 - Y
231             # }
232             # $x += $power * $ndigit;
233             # }
234             # @ndigits || last;
235             # {
236             # my $ndigit = shift @ndigits; # low to high
237             # $y += $power * $ndigit;
238             # $power *= $radix;
239             #
240             # if ($ndigit & 1) {
241             # $x = $power-1 - $x;
242             # }
243             # }
244             # }
245             # return ($x, $y);
246             }
247              
248             sub xy_to_n {
249 1     1 1 8 my ($self, $x, $y) = @_;
250             ### PeanoCurve xy_to_n(): "$x, $y"
251              
252 1         4 $x = round_nearest ($x);
253 1         3 $y = round_nearest ($y);
254              
255 1 50 33     16 if ($x < 0 || $y < 0) {
256 0         0 return undef;
257             }
258 1 50       6 if (is_infinite($x)) {
259 0         0 return $x;
260             }
261 1 50       4 if (is_infinite($y)) {
262 0         0 return $y;
263             }
264              
265 1         3 my $radix = $self->{'radix'};
266 1         4 my $zero = ($x * 0 * $y); # inherit bignum 0
267              
268 1         3 my @x = digit_split_lowtohigh ($x, $radix);
269 1         3 my @y = digit_split_lowtohigh ($y, $radix);
270              
271 1         2 my $radix_minus_1 = $radix - 1;
272 1         2 my $xk = 0;
273 1         2 my $yk = 0;
274              
275 1         2 my @n; # stored low to high, generated from high to low
276 1         25 my $i_high = max($#x,$#y);
277 1         3 my $npos = 2*$i_high+1;
278              
279 1         4 foreach my $i (reverse 0 .. $i_high) { # high to low
280             {
281 0   0     0 my $digit = $y[$i] || 0;
282 0 0       0 if ($yk & 1) {
283 0         0 $digit = $radix_minus_1 - $digit; # reverse digit
284             }
285 0         0 $n[$npos--] = $digit;
286 0         0 $xk ^= $digit;
287             }
288             {
289 0   0     0 my $digit = $x[$i] || 0;
  0         0  
  0         0  
290 0 0       0 if ($xk & 1) {
291 0         0 $digit = $radix_minus_1 - $digit; # reverse digit
292             }
293 0         0 $n[$npos--] = $digit;
294 0         0 $yk ^= $digit;
295             }
296             }
297 1         4 return digit_join_lowtohigh (\@n, $radix, $zero);
298             }
299              
300             # exact
301             sub rect_to_n_range {
302 1     1 1 8 my ($self, $x1,$y1, $x2,$y2) = @_;
303              
304 1         4 $x1 = round_nearest ($x1);
305 1         4 $y1 = round_nearest ($y1);
306 1         3 $x2 = round_nearest ($x2);
307 1         3 $y2 = round_nearest ($y2);
308 1 50       4 ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
309 1 50       3 ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
310             ### rect_to_n_range(): "$x1,$y1 to $x2,$y2"
311              
312 1 50 33     15 if ($x2 < 0 || $y2 < 0) {
313 0         0 return (1, 0);
314             }
315              
316 1         5 my $radix = $self->{'radix'};
317              
318 1         4 my ($power, $level) = round_down_pow (max($x2,$y2), $radix);
319 1 50       3 if (is_infinite($level)) {
320 0         0 return (0, $level);
321             }
322              
323 1         78 my $n_power = $power * $power * $radix;
324 1         4 my $max_x = 0;
325 1         1 my $max_y = 0;
326 1         2 my $max_n = 0;
327 1         2 my $max_xk = 0;
328 1         1 my $max_yk = 0;
329              
330 1         2 my $min_x = 0;
331 1         2 my $min_y = 0;
332 1         2 my $min_n = 0;
333 1         2 my $min_xk = 0;
334 1         1 my $min_yk = 0;
335              
336             # l<=c
337             # l>c2 or h-1
338             # l>c2 or h<=c1
339             # so does overlap if
340             # l<=c2 and h>c1
341             #
342 1         3 my $radix_minus_1 = $radix - 1;
343             my $overlap = sub {
344 5     5   11 my ($c,$ck,$digit, $c1,$c2) = @_;
345 5 100       11 if ($ck & 1) {
346 1         2 $digit = $radix_minus_1 - $digit;
347             }
348             ### overlap consider: "inv".($ck&1)."digit=$digit ".($c+$digit*$power)."<=c<".($c+($digit+1)*$power)." cf $c1 to $c2 incl"
349 5   66     27 return ($c + $digit*$power <= $c2
350             && $c + ($digit+1)*$power > $c1);
351 1         6 };
352              
353 1         4 while ($level-- >= 0) {
354             ### $power
355             ### $n_power
356             ### $max_n
357             ### $min_n
358             {
359 1         2 my $digit;
360 1         3 for ($digit = $radix_minus_1; $digit > 0; $digit--) {
361 2 100       5 last if &$overlap ($max_y,$max_yk,$digit, $y1,$y2);
362             }
363 1         3 $max_n += $n_power * $digit;
364 1         2 $max_xk ^= $digit;
365 1 50       4 if ($max_yk&1) { $digit = $radix_minus_1 - $digit; }
  0         0  
366 1         2 $max_y += $power * $digit;
367             ### max y digit (complemented): $digit
368             ### $max_y
369             ### $max_n
370             }
371             {
372 1         2 my $digit;
  1         2  
  1         9  
373 1         4 for ($digit = 0; $digit < $radix_minus_1; $digit++) {
374 1 50       3 last if &$overlap ($min_y,$min_yk,$digit, $y1,$y2);
375             }
376 1         2 $min_n += $n_power * $digit;
377 1         2 $min_xk ^= $digit;
378 1 50       3 if ($min_yk&1) { $digit = $radix_minus_1 - $digit; }
  0         0  
379 1         3 $min_y += $power * $digit;
380             ### min y digit (complemented): $digit
381             ### $min_y
382             ### $min_n
383             }
384              
385 1         3 $n_power = int($n_power/$radix);
386             {
387 1         2 my $digit;
388 1         3 for ($digit = $radix_minus_1; $digit > 0; $digit--) {
389 1 50       2 last if &$overlap ($max_x,$max_xk,$digit, $x1,$x2);
390             }
391 1         3 $max_n += $n_power * $digit;
392 1         2 $max_yk ^= $digit;
393 1 50       3 if ($max_xk&1) { $digit = $radix_minus_1 - $digit; }
  1         7  
394 1         4 $max_x += $power * $digit;
395             ### max x digit (complemented): $digit
396             ### $max_x
397             ### $max_n
398             }
399             {
400 1         1 my $digit;
  1         2  
  1         1  
401 1         4 for ($digit = 0; $digit < $radix_minus_1; $digit++) {
402 1 50       10 last if &$overlap ($min_x,$min_xk,$digit, $x1,$x2);
403             }
404 1         3 $min_n += $n_power * $digit;
405 1         3 $min_yk ^= $digit;
406 1 50       3 if ($min_xk&1) { $digit = $radix_minus_1 - $digit; }
  0         0  
407 1         3 $min_x += $power * $digit;
408             ### min x digit (complemented): $digit
409             ### $min_x
410             ### $min_n
411             }
412              
413 1         3 $power = int($power/$radix);
414 1         4 $n_power = int($n_power/$radix);
415             }
416             ### is: "$min_n at $min_x,$min_y to $max_n at $max_x,$max_y"
417 1         6 return ($min_n, $max_n);
418             }
419              
420             #------------------------------------------------------------------------------
421             # levels
422              
423 4     4   1685 use Math::PlanePath::ZOrderCurve;
  4         12  
  4         266  
424             *level_to_n_range = \&Math::PlanePath::ZOrderCurve::level_to_n_range;
425             *n_to_level = \&Math::PlanePath::ZOrderCurve::n_to_level;
426              
427             #------------------------------------------------------------------------------
428             1;
429             __END__