File Coverage

lib/Math/Pell.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             #
2             # This file is part of Math-Pell
3             #
4             # This software is copyright (c) 2010 by Stefan Petrea.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9 1     1   49141 use strict;
  1         3  
  1         42  
10 1     1   7 use warnings;
  1         2  
  1         60  
11             package Math::Pell;
12             our $VERSION = '0.6';
13 1     1   2837 use Moose;
  0            
  0            
14             #use Math::Counting;
15             use Math::BigInt;
16             use POSIX qw(ceil floor);
17             use Carp qw/confess/;
18             use List::AllUtils qw/any/;
19              
20             =pod
21              
22             =head1 NAME
23              
24             Math::Pell - Solution for Pell equations
25              
26              
27             =head1 VERSION
28              
29             version 0.6
30              
31             =head1 DESCRIPTION
32              
33             This module solves the Pell equation by finding a minimal solution and making a method available for
34             generating all solutions to the Pell equation:
35              
36             x^2 - Dy^2 = 1
37              
38             Of particular interest are non-trivial solutions to this equation.
39             The numbers x and y are integers.
40              
41              
42              
43             =head1 SYNOPSIS
44              
45             Finding the first 5 solutions of x^2 - 7y^2 = 1
46              
47             use Math::Pell;
48             my $p = Math::Pell->new({D=>7});# equation is x^2 - Dy^2 = 1 where x,y are integers
49             $p->find_minimal_sol;
50             printf "(%d,%d)\n",$p->nth_solution($_)
51             for 1..5;
52              
53              
54             (8,3)
55             (127,48)
56             (2024,765)
57             (32257,12192)
58             (514088,194307)
59              
60             =head1 SAMPLE TEST OUTPUT
61              
62             sqrt[13]= [3,1,1,1,1,6]
63             n= 2 p=4 q=1 p/q = 4
64             n= 3 p=7 q=2 p/q = 3
65             n= 4 p=11 q=3 p/q = 3
66             n= 5 p=18 q=5 p/q = 3
67             n= 6 p=119 q=33 p/q = 3
68             n= 7 p=137 q=38 p/q = 3
69             n= 8 p=256 q=71 p/q = 3
70             n= 9 p=393 q=109 p/q = 3
71             n= 10 p=649 q=180 p/q = 3
72             Minimal solution -> (649,180)
73             ok 14 - (649,180) is a working solution 1
74             ok 15 - (1093435849,303264540) is a working solution 1
75             ok 16 - (1419278889601,393637139280) is a working solution 1
76             ok 17 - (1842222905266249,510940703520900) is a working solution 1
77             ok 18 - (2391203911756701601,663200639532988920) is a working solution 1
78             ok 19 - (3103780835237293411849,860833919173116097260) is a working solution 1
79             ok 20 - (4028705132934095091878401,1117361763886065161254560) is a working solution 1
80             ok 21 - (5229256158767620191964752649,1450334708690193406192321620) is a working solution 1
81             ok 22 - (6787570465375238075075157060001,1882533334518107155172472208200) is a working solution 1
82             ok 23 - (8810261234800900253827361899128649,2443526817869794397220462733921980) is a working solution 1
83             ok 24 - (11435712295201103154229840669911926401,3171695927061658609485005456158521840) is a working solution 1
84             ok 25 - (14843545748909797093290079362183781339849,4116858869799215005317139861631027426340) is a working solution 1
85             ok 26 - (19266910946372621425987368782273878267197601,5343679641303454015243038055391617440867480) is a working solution 1
86             ....
87              
88              
89              
90              
91              
92              
93             sqrt[61]= [7,1,4,3,1,2,2,1,3,4,1,14]
94             ...
95             Minimal solution -> (1766319049,226153980)
96             ok 14 - (1766319049,226153980) is a working solution 1
97             ok 15 - (22042834973108102061352541449,2822295814832482312327709940) is a working solution 1
98             ok 16 - (77869358613928486808166555366140995201,9970149719303180503641083029374964080) is a working solution 1
99             ....
100              
101              
102             =head1 CF2fraction($max_iter,$conv_callback,@cont_fraction)
103              
104             =over
105              
106             =item * @cont_fraction is the continued fraction form of the square root the non-periodic
107             and periodic parts are provided here.
108              
109             =item * $conv_callback is a subref which is called whenever a new convergent is found and it's passed as argument
110             to the callback
111              
112             =item * $max_iter is the maximum iteration
113              
114             =back
115              
116             =head1 nth_solution($i)
117              
118             if a minimal solution a+b\sqrt{D} is found , then all solutions can be found by expanding (a+b\sqrt{D})^i = A+B\sqrt{D}
119             so (A,B) is also a solution.
120              
121             =head1 cfrac(D)
122              
123             computes the continued fraction representation of a square root D. it can be proved that the continued fraction will be periodic.
124              
125             =head1 find_minimal_sol()
126              
127             returns a minimal solution for the Pell equation by finding the first convergent of \sqrt{D} for which it's numerator and denominator are solutions to the Pell equation.
128              
129             =head1 BIBLIOGRAPHY
130              
131             [1] L. Panaitopol A. Gica - O Introducere in aritmetica si Teoria Numerelor
132              
133             =head1 SEE ALSO
134              
135             L<http://en.wikipedia.org/wiki/Pell's_equation>
136              
137             =head1 AUTHOR
138              
139             Stefan Petrea, C<< <stefan.petrea at gmail.com> >>
140              
141              
142             =cut
143              
144              
145              
146             has minimal_sol => (
147             isa =>'ArrayRef[Math::BigInt]',
148             is =>'rw',
149             lazy => 1,
150             default => sub {[]},
151             );
152              
153             has $_ => (
154             isa => 'Int',
155             is => 'rw',
156             default => 0,
157             ) for qw/D debug/;
158              
159              
160             sub BUILDARGS {
161             my ($self,$param) = @_;
162              
163             confess "D is supposed to be square-free, you passed $param->{D}"
164             if
165             any { $param->{D} % $_ == 0 }
166             map { $_**2 } 2..$param->{D}/2;
167              
168             { D=> $param->{D} };
169             };
170              
171              
172             # gets the continued fraction of a square root
173             sub cfrac {
174             my ($self,$D) = @_;
175             my ($n,@m,@d,@a);
176             my $i_rep;#index where a_n starts to repeat
177             my $repeating; #shows if the sequence is repeating or not
178              
179             ($repeating,$i_rep,$n,$m[0],$d[0],$a[0] ) =
180             (0 , -1, 0, 0, 1,floor(sqrt($D)) );
181            
182             while(1) {
183             ++$n;
184             $m[$n] = $d[$n-1]*$a[$n-1] - $m[$n-1];
185             $d[$n] = ($D-$m[$n]**2)/$d[$n-1];
186             $a[$n] = floor(($a[0]+$m[$n])/$d[$n]);
187              
188             for (reverse(1..$n-1)) {
189             $repeating=
190             $m[$_] == $m[$n] &&
191             $d[$_] == $d[$n] &&
192             $a[$_] == $a[$n];
193             if($repeating) {
194             $i_rep = $_;
195             last;
196             }
197             };
198             last if $repeating;
199             };
200             pop @a;
201             return @a;
202             }
203              
204              
205             # computing a convergent with the continued fraction representation
206             # using http://en.wikipedia.org/wiki/Fundamental_recurrence_formulas
207             # code->(p,q) is run for each of the convergents
208             sub CF2fraction {
209             my ($self,$max_iter,$code,@b) = @_;
210             # print Dumper \@b;
211             my @p = ($b[0],$b[1]*$b[0]+1);
212             my @q = (1 ,$b[1] );
213              
214             $p[$_] = Math::BigInt->new($p[$_]) for qw/0 1/;
215             $q[$_] = Math::BigInt->new($q[$_]) for qw/0 1/;
216              
217             return if
218             $code->($p[0],$q[0]) ||
219             $code->($p[1],$q[1]) ;
220              
221             my $n=2;
222             my $jump=0; # jump over the floor part
223              
224             while(1) {
225             print "n= $n p=$p[$n-1] q=$q[$n-1] p/q = ".$p[$n-1]/$q[$n-1]."\n"
226             if $self->debug;
227             if($code) {
228             last if $code->($p[$n-1],$q[$n-1]); # stop if code returns something defined or 1
229             };
230             $p[$n] = Math::BigInt->new(0);
231             $q[$n] = Math::BigInt->new(0);
232              
233             my $j = ($n+$jump)%@b; # need to jump over the first number which is the floor of the square root
234             # and keep track of the jump over iterations
235            
236             $j=1,$jump++ if $j==0; # $b[0] is used when $j==0 and we want to avoid that because
237             # that's the first number in the continued fraction representation
238             # and it's not part of the periodic part
239              
240             $p[$n] = $b[$j]*$p[$n-1] + $p[$n-2];
241             $q[$n] = $b[$j]*$q[$n-1] + $q[$n-2];
242             last if $n++ > $max_iter;
243             };
244             }
245              
246             # returns true if parameters are a solution to the eq and false otherwise
247             sub is_solution {
248             my ($self,$a,$b) = @_;
249             return $a**2 - ($self->D * ($b**2)) == 1;
250             }
251              
252              
253              
254             sub iterate_convergents {
255             my ($self,$lim,$code) = @_;
256             $self->CF2fraction(
257             $lim,
258             sub { $code->(@_) },
259             $self->cfrac($self->D)
260             );
261             }
262              
263             sub find_minimal_sol {
264             my ($self) = @_;
265             my @min_sol;
266              
267             #search a solution to the Pell equation through the first 100 convergents of sqrt(D)
268             $self->iterate_convergents(
269             100,
270             sub {
271             if( $self->is_solution(@_) ) { # the Pell equation
272             @min_sol = @_;
273             return 1;# stop if minimal solution is found
274             };
275             0;
276             }
277             );
278             $self->minimal_sol([@min_sol]);
279             return @min_sol;
280             }
281              
282              
283             sub nth_solution {
284             my ($self,$i) = @_;
285             my ($a,$b) = @{$self->minimal_sol};
286             return ($a,$b) if $i==1;
287              
288             my ($A,$B) =
289             map { Math::BigInt->new($_) }
290             ($a,$b);
291              
292             for(2..$i){
293             my $oldA = $A->copy(); #old values for $A
294             $A = $a*$A + $self->D * $b*$B;
295             $B = $a*$B + $b*$oldA;
296             };
297             return ($A,$B);
298             }
299              
300             1;