File Coverage

blib/lib/Math/Polynomial/ModInt.pm
Criterion Covered Total %
statement 194 194 100.0
branch 68 68 100.0
condition 27 27 100.0
subroutine 36 36 100.0
pod 17 17 100.0
total 342 342 100.0


line stmt bran cond sub pod time code
1             # Copyright (c) 2013-2019 by Martin Becker. This package is free software,
2             # licensed under The Artistic License 2.0 (GPL compatible).
3              
4             package Math::Polynomial::ModInt;
5              
6 3     3   61919 use 5.006;
  3         20  
7 3     3   14 use strict;
  3         4  
  3         49  
8 3     3   10 use warnings;
  3         12  
  3         89  
9 3     3   1962 use Math::BigInt try => 'GMP';
  3         47963  
  3         15  
10 3     3   45986 use Math::ModInt 0.012;
  3         5755  
  3         135  
11 3     3   17 use Math::ModInt::Event qw(UndefinedResult);
  3         6  
  3         123  
12 3     3   1806 use Math::Polynomial 1.015;
  3         18720  
  3         83  
13 3     3   19 use Carp qw(croak);
  3         5  
  3         134  
14              
15             # ----- object definition -----
16              
17             # Math::Polynomial::ModInt=ARRAY(...)
18              
19             # .............. index .............. # .......... value ..........
20              
21 3     3   15 use constant _OFFSET => Math::Polynomial::_NFIELDS;
  3         10  
  3         148  
22 3     3   17 use constant _F_INDEX => _OFFSET + 0; # ordinal number i, 0 <= i
  3         5  
  3         132  
23 3     3   16 use constant _NFIELDS => _OFFSET + 1;
  3         5  
  3         286  
24              
25             # ----- class data -----
26              
27             BEGIN {
28 3     3   26 require Exporter;
29 3         81 our @ISA = qw(Math::Polynomial Exporter);
30 3         8 our @EXPORT_OK = qw(modpoly);
31 3         6 our $VERSION = '0.004';
32 3         4957 our @CARP_NOT = qw(Math::ModInt::Event::Trap Math::Polynomial);
33             }
34              
35             my $default_string_config = {
36             convert_coeff => sub { $_[0]->residue },
37             times => q[*],
38             wrap => \&_wrap,
39             };
40              
41             my $lifted_string_config = {
42             times => q[*],
43             fold_sign => 1,
44             };
45              
46             my $ipol = Math::Polynomial->new(Math::BigInt->new);
47             $ipol->string_config($lifted_string_config);
48              
49             # ----- private subroutines -----
50              
51             # event catcher
52             sub _bad_modulus {
53 3     3   506 croak 'modulus not prime';
54             }
55              
56             # event catcher
57             sub _no_inverse {
58 1     1   291 croak 'undefined inverse';
59             }
60              
61             # wrapper for modular inverse to bail out early if not in a field
62             sub _inverse {
63 2     2   3 my ($element) = @_;
64 2         14 my $trap = UndefinedResult->trap(\&_no_inverse);
65 2         51 return $element->inverse;
66             }
67              
68             # Does a set of coefficient vectors have a zero linear combination?
69             # We transform and include them one by one into a matrix in echelon form.
70             # Argument is an iterator so we can short-cut generating superfluous vectors.
71             # Supplied vectors may be modified.
72             sub _linearly_dependent {
73 7     7   11 my ($it) = @_;
74 7         25 my $trap = UndefinedResult->trap(\&_bad_modulus);
75 7         126 my @ech = ();
76 7         12 while (defined(my $vec = $it->())) {
77 22         624 my $ex = 0;
78 22         38 for (; $ex < @ech; ++$ex) {
79 25         77 my $evec = $ech[$ex];
80 25 100       28 last if @{$evec} < @{$vec};
  25         26  
  25         43  
81 20 100       24 if (@{$evec} == @{$vec}) {
  20         24  
  20         34  
82 16         18 my $i = $#{$vec};
  16         20  
83 16 100       30 return 1 if !$i;
84 15         18 my $w = pop(@{$vec}) / $evec->[$i];
  15         28  
85 14         213 while ($i-- > 0) {
86 42         544 $vec->[$i] -= $evec->[$i] * $w;
87             }
88 14   100     283 while (@{$vec} && !$vec->[-1]) {
  25         56  
89 11         71 pop(@{$vec});
  11         15  
90             }
91             }
92             }
93 20 100       62 return 1 if !@{$vec};
  20         40  
94 19         51 splice @ech, $ex, 0, $vec;
95             }
96 4         16 return 0;
97             }
98              
99             # ----- private methods -----
100              
101             # wrapper to decorate stringified polynomial
102             sub _wrap {
103 478     478   12434 my ($this, $text) = @_;
104 478         729 my $modulus = $this->modulus;
105 478         3492 return "$text (mod $modulus)";
106             }
107              
108             # ----- protected methods -----
109              
110             # constructor with index argument
111             sub _xnew {
112 148     148   215 my $this = shift;
113 148         167 my $index = shift;
114 148         230 my $poly = $this->new(@_);
115 148         7991 $poly->[_F_INDEX] = $index;
116 148         465 return $poly;
117             }
118              
119             # ----- overridden public methods -----
120              
121             sub new {
122 499     499 1 21803 my $this = shift;
123 499 100 100     825 if (!@_ && !ref $this) {
124 1         122 croak 'insufficient arguments';
125             }
126 498 100       699 if (grep {$_->is_undefined} @_) {
  1638         5482  
127 2         34 _bad_modulus();
128             }
129 496         2520 return $this->SUPER::new(@_);
130             }
131              
132             sub string_config {
133 963     963 1 45473 my $this = shift;
134 963 100       2154 return $this->SUPER::string_config(@_) if ref $this;
135 482 100       740 ($default_string_config) = @_ if @_;
136 482         2835 return $default_string_config;
137             }
138              
139             sub is_equal {
140 10     10 1 831 my ($this, $that) = @_;
141 10         21 my $i = $this->degree;
142 10   100     46 my $eq = $this->modulus == $that->modulus && $i == $that->degree;
143 10   100     106 while ($eq && 0 <= $i) {
144 22         37 $eq = $this->coeff($i)->residue == $that->coeff($i)->residue;
145 22         316 --$i;
146             }
147 10         37 return $eq;
148             }
149              
150             sub is_unequal {
151 5     5 1 7 my ($this, $that) = @_;
152 5         12 my $i = $this->degree;
153 5   100     24 my $eq = $this->modulus == $that->modulus && $i == $that->degree;
154 5   100     47 while ($eq && 0 <= $i) {
155 10         15 $eq = $this->coeff($i)->residue == $that->coeff($i)->residue;
156 10         146 --$i;
157             }
158 5         17 return !$eq;
159             }
160              
161             sub is_monic {
162 55     55 1 2461 my ($this) = @_;
163 55         103 my $degree = $this->degree;
164 55   100     274 return 0 <= $degree && $this->coeff($degree)->residue == 1;
165             }
166              
167             sub monize {
168 4     4 1 15 my ($this) = @_;
169 4         12 my $degree = $this->degree;
170 4 100       21 return $this if $degree < 0;
171 3         8 my $leader = $this->coeff($degree);
172 3 100       26 return $this if $leader->residue == 1;
173 2         10 return $this->mul_const(_inverse($leader));
174             }
175              
176             # ----- public subroutine -----
177              
178 146     146 1 7721 sub modpoly { __PACKAGE__->from_index(@_) }
179              
180             # ----- class-specific public methods -----
181              
182             sub from_index {
183 149     149 1 1970 my ($this, $index, $modulus) = @_;
184 149         182 my $zero;
185 149 100       248 if (defined $modulus) {
    100          
186 147         255 $zero = Math::ModInt::mod(0, $modulus);
187             }
188             elsif (ref $this) {
189 1         4 $zero = $this->coeff_zero;
190 1         5 $modulus = $zero->modulus;
191             }
192             else {
193 1         222 croak('usage error: modulus parameter missing');
194             }
195 148         14698 my @coeff = ();
196 148         178 my $q = $index;
197 148         275 while ($q > 0) {
198 353         618 ($q, my $r) = $zero->new2($q);
199 353         5882 push @coeff, $r;
200             }
201 148 100       349 return $this->_xnew(@coeff? ($index, @coeff): (0, $zero));
202             }
203              
204             sub from_int_poly {
205 3     3 1 1200 my ($this, $poly, $modulus) = @_;
206 3 100       9 if (!defined $modulus) {
207 2 100       6 if (!ref $this) {
208 1         143 croak('usage error: modulus parameter missing');
209             }
210 1         3 $modulus = $this->modulus;
211             }
212 2         9 my @coeff = map { Math::ModInt::mod($_, $modulus) } $poly->coefficients;
  12         250  
213 2         42 return $this->new(@coeff);
214             }
215              
216             sub modulus {
217 1264     1264 1 3880 my ($this) = @_;
218 1264         1833 return $this->coeff_zero->modulus;
219             }
220              
221             sub index {
222 6     6 1 617 my ($this) = @_;
223 6         11 my $base = $this->modulus;
224 6         24 my $index = $this->[_F_INDEX];
225 6 100       16 if (!defined $index) {
226 2         12 $index = Math::BigInt->new;
227 2         154 foreach my $c (reverse $this->coeff) {
228 12         2681 $index->bmul($base)->badd($c->residue);
229             }
230 2         419 $this->[_F_INDEX] = $index;
231             }
232 6         17 return $index;
233             }
234              
235 181     181 1 3452 sub number_of_terms { scalar grep { $_->is_not_zero } $_[0]->coeff }
  642         3568  
236              
237 1     1 1 425 sub lift { $ipol->new(map { $_->residue } $_[0]->coefficients) }
  6         38  
238              
239 2     2 1 3478 sub centerlift { $ipol->new(map { $_->centered_residue } $_[0]->coefficients) }
  8         61  
240              
241             sub lambda_reduce {
242 8     8 1 1446 my ($this, $lambda) = @_;
243 8         17 my $degree = $this->degree;
244 8 100       37 return $this if $degree <= $lambda;
245 7         14 my @coeff = map { $this->coeff($_) } 0 .. $lambda;
  22         107  
246 7         54 for (my $i = 1, my $n = $lambda+1; $n <= $degree; ++$i, ++$n) {
247 16         27 $coeff[$i] += $this->coeff($n);
248 16 100       260 $i = 0 if $i == $lambda;
249             }
250 7         12 return $this->new(@coeff);
251             }
252              
253             sub first_root {
254 16     16 1 37 my ($this) = @_;
255 16         28 my $zero = $this->coeff_zero;
256 16         48 my $lsc = $this->coeff(0);
257 16 100       112 return $zero if $lsc->is_zero;
258 15 100       99 if ($this->degree == 1) {
259 5         23 my $mscr = $this->coeff(1)->centered_residue;
260 5 100       87 return -$lsc if $mscr == 1;
261 4 100       13 return $lsc if $mscr == -1;
262             }
263 12         51 foreach my $n (1 .. $zero->modulus - 1) {
264 66         6647 my $root = $zero->new($n);
265 66 100       578 return $root if $this->evaluate($root)->is_zero;
266             }
267 9         684 return undef;
268             }
269              
270             # implementation restriction: defined for prime moduli only
271             sub is_irreducible {
272 16     16 1 549 my ($this) = @_;
273 16         33 my $n = $this->degree;
274 16 100       78 return 0 if $n <= 0;
275 15 100       23 return 1 if $n == 1;
276 14 100       28 return 0 if !$this->coeff(0);
277 13 100       235 return 0 if $this->gcd($this->differentiate)->degree > 0;
278 11         250 my $p = $this->modulus;
279             # optimization: O(p) zero search only for small p or very large n
280 11 100 100     70 if ($p <= 43 || log($p - 20) <= $n * 0.24 + 2.68) {
281 10 100 100     47 my $rp = 2 < $p && $p <= $n? $this->lambda_reduce($p-1): $this;
282 10 100       184 return 0 if defined $rp->first_root;
283 8 100       24 return 1 if $n <= 3;
284             }
285             # Berlekamp rank < $n - 1?
286 7         20 my $xp = $this->exp_mod($p);
287 7         248 my $aj = $xp;
288 7         17 my $bj = $this->monomial(1);
289 7         136 my $j = 0;
290             return 0 if _linearly_dependent(
291             sub {
292 26 100   26   54 return undef if $j >= $n - 1;
293 22 100       35 if ($j++) {
294 15         27 $aj = $aj * $xp % $this;
295 15         417 $bj <<= 1;
296             }
297 22         315 return [($aj - $bj)->coeff];
298             }
299 7 100       34 );
300 4         80 return 1;
301             }
302              
303             1;
304              
305             __END__