File Coverage

blib/lib/Math/PlanePath/TheodorusSpiral.pm
Criterion Covered Total %
statement 111 125 88.8
branch 13 20 65.0
condition 0 3 0.0
subroutine 27 28 96.4
pod 4 4 100.0
total 155 180 86.1


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             # Hlawka, angles of point N is
20             # phi(n) = sum k=1 to n of arcsin 1/sqrt(k+1)
21             # is equidistributed mod 2pi
22              
23              
24             package Math::PlanePath::TheodorusSpiral;
25 1     1   1126 use 5.004;
  1         4  
26 1     1   5 use strict;
  1         2  
  1         23  
27 1     1   407 use Math::Libm 'hypot';
  1         3277  
  1         89  
28             #use List::Util 'max';
29             *max = \&Math::PlanePath::_max;
30              
31 1     1   6 use vars '$VERSION', '@ISA';
  1         2  
  1         61  
32             $VERSION = 127;
33 1     1   718 use Math::PlanePath;
  1         2  
  1         41  
34             @ISA = ('Math::PlanePath');
35              
36             use Math::PlanePath::Base::Generic
37 1         44 'is_infinite',
38 1     1   7 'round_nearest';
  1         1  
39              
40             # uncomment this to run the ### lines
41             #use Smart::Comments;
42              
43              
44 1     1   6 use constant n_start => 0;
  1         1  
  1         47  
45 1     1   5 use constant figure => 'circle';
  1         2  
  1         38  
46 1     1   5 use constant x_negative_at_n => 4;
  1         1  
  1         36  
47 1     1   5 use constant y_negative_at_n => 7;
  1         3  
  1         36  
48 1     1   5 use constant gcdxy_maximum => 1;
  1         2  
  1         38  
49 1     1   6 use constant dx_minimum => -1; # supremum when straight
  1         2  
  1         37  
50 1     1   5 use constant dx_maximum => 1; # at N=0
  1         2  
  1         54  
51 1     1   7 use constant dy_minimum => -1;
  1         2  
  1         44  
52 1     1   5 use constant dy_maximum => 1; # at N=1
  1         2  
  1         57  
53 1     1   6 use constant dsumxy_minimum => -sqrt(2); # supremum diagonal
  1         2  
  1         51  
54 1     1   7 use constant dsumxy_maximum => sqrt(2);
  1         2  
  1         45  
55 1     1   5 use constant ddiffxy_minimum => -sqrt(2); # supremum diagonal
  1         2  
  1         52  
56 1     1   6 use constant ddiffxy_maximum => sqrt(2);
  1         2  
  1         40  
57 1     1   5 use constant turn_any_right => 0; # left always
  1         2  
  1         52  
58 1     1   5 use constant turn_any_straight => 0; # left always
  1         3  
  1         95  
59              
60              
61             #------------------------------------------------------------------------------
62              
63             # This adding up of unit steps isn't very good. The last x,y,n is kept
64             # anticipating successively higher n, not necessarily consecutive, plus past
65             # x,y,n at _SAVE intervals for going backwards.
66             #
67             # The simplest formulas for the polar angle, possibly with the analytic
68             # continuation version don't seem much better, but theta approaches
69             # 2*sqrt(N) + const, or 2*sqrt(N) + 1/(6*sqrt(N+1)) + const + O(n^(3/2)), so
70             # more terms of that might have tolerably rapid convergence.
71             #
72             # The arctan sums for the polar angle end up as the generalized Riemann
73             # zeta, or the generalized minus the plain. Is there a good formula for
74             # that which would converge quickly?
75              
76 1     1   7 use constant 1.02; # for leading underscore
  1         17  
  1         25  
77 1     1   4 use constant _SAVE => 1000;
  1         2  
  1         613  
78              
79             my @save_n = (1);
80             my @save_x = (1);
81             my @save_y = (0);
82             my $next_save = _SAVE;
83              
84             sub new {
85 2     2 1 143 return shift->SUPER::new (i => 1,
86             x => 1,
87             y => 0,
88             @_);
89             }
90              
91             # r = sqrt(int)
92             # (frac r)^2
93             # = hypot(r, frac)^2 frac at right angle to radial
94             # = r^2 + $frac^2
95             # = sqrt(int)^2 + $frac^2
96             # = $int + $frac^2
97             #
98             sub n_to_rsquared {
99 10     10 1 199 my ($self, $n) = @_;
100 10 50       25 if ($n < 0) { return undef; }
  0         0  
101 10         20 my $int = int($n);
102 10         16 $n -= $int; # fractional part
103 10         43 return $n*$n + $int;
104             }
105              
106             # r = sqrt(i)
107             # x,y angle
108             # r*x/hypot, r*y/hypot
109             #
110             # newx = x - y/r
111             # newy = y + x/r
112             # (x-y/r)^2 + (y+x/r)^2
113             # = x^2 - 2y/r + y^2/r^2
114             # + y^2 + 2x/r + x^2/r^2
115              
116             sub n_to_xy {
117 10     10 1 489 my ($self, $n) = @_;
118             #### TheodorusSpiral n_to_xy(): $n
119              
120 10 50       28 if ($n < 0) { return; }
  0         0  
121 10 50       25 if (is_infinite($n)) { return ($n,$n); }
  0         0  
122              
123 10 100       24 if ($n < 1) {
124 2         7 return ($n, 0);
125             }
126 8         15 my $frac = $n;
127 8         14 $n = int($n);
128 8         14 $frac -= $n;
129              
130 8         18 my $i = $self->{'i'};
131 8         12 my $x = $self->{'x'};
132 8         13 my $y = $self->{'y'};
133             #### n_to_xy(): "$n from state $i $x,$y"
134              
135 8 100       16 if ($i > $n) {
136 2         8 for (my $pos = $#save_n; $pos >= 0; $pos--) {
137 5 100       13 if ($save_n[$pos] <= $n) {
138 2         3 $i = $save_n[$pos];
139 2         4 $x = $save_x[$pos];
140 2         4 $y = $save_y[$pos];
141 2         6 last;
142             }
143             }
144             ### resume: "$i $x,$y"
145             }
146              
147 8         17 while ($i < $n) {
148 3112         4418 my $r = sqrt($i);
149 3112         17782 ($x,$y) = ($x - $y/$r, $y + $x/$r);
150 6224         3896 $i++;
151              
152 6224 100       6230 if ($i == $next_save) {
153 2         6 push @save_n, $i;
154 2         3 push @save_x, $x;
155 2         5 push @save_y, $y;
156 2         6 $next_save += _SAVE;
157              
158             ### save: $i
159             ### @save_n
160             ### @save_x
161             ### @save_y
162             }
163             }
164              
165 3120         17 $self->{'i'} = $i;
166 3120         11 $self->{'x'} = $x;
167 3120         12 $self->{'y'} = $y;
168              
169 3120 100       19 if ($frac) {
170 3         6 my $r = sqrt($n);
171 3         15 return ($x - $frac*$y/$r,
172             $y + $frac*$x/$r);
173             } else {
174             #### integer return: "$i $x,$y"
175 3117         22 return ($x,$y);
176             }
177             }
178              
179             sub xy_to_n {
180 0     0 1   my ($self, $x, $y) = @_;
181             ### TheodorusSpiral xy_to_n(): "$x, $y"
182 0           my $r = hypot ($x,$y);
183 0           my $n_lo = int (max (0, $r - .51) ** 2);
184 0           my $n_hi = int (($r + .51) ** 2);
185             ### $n_lo
186             ### $n_hi
187              
188 0 0 0       if (is_infinite($n_lo) || is_infinite($n_hi)) {
189             ### infinite range, r inf or too big ...
190 0           return undef;
191             }
192              
193             # for(;;) loop since $n_lo..$n_hi limited to IV range
194 0           for (my $n = $n_lo; $n <= $n_hi; $n += 1) {
195 0           my ($nx,$ny) = $self->n_to_xy($n);
196             #### $n
197             #### $nx
198             #### $ny
199             #### hypot: hypot ($x-$nx,$y-$ny)
200 0 0         if (hypot ($x-$nx,$y-$ny) <= 0.5) {
201 0           return $n;
202             }
203             }
204 0           return undef;
205             }
206              
207 1     1   529 use Math::PlanePath::SacksSpiral;
  1         4  
  1         54  
208             # not exact
209             *rect_to_n_range = \&Math::PlanePath::SacksSpiral::rect_to_n_range;
210              
211             1;
212             __END__