File Coverage

blib/lib/Math/Polynomial/ModInt.pm
Criterion Covered Total %
statement 199 199 100.0
branch 72 72 100.0
condition 27 27 100.0
subroutine 37 37 100.0
pod 17 17 100.0
total 352 352 100.0


line stmt bran cond sub pod time code
1             # Copyright (c) 2013-2022 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   68077 use 5.006;
  3         27  
7 3     3   13 use strict;
  3         5  
  3         51  
8 3     3   11 use warnings;
  3         12  
  3         77  
9 3     3   2277 use Math::BigInt try => 'GMP';
  3         60199  
  3         18  
10 3     3   43399 use Math::ModInt 0.012;
  3         5380  
  3         135  
11 3     3   18 use Math::ModInt::Event qw(UndefinedResult);
  3         8  
  3         118  
12 3     3   1819 use Math::Polynomial 1.015;
  3         18105  
  3         98  
13 3     3   20 use Carp qw(croak);
  3         6  
  3         163  
14              
15             # ----- object definition -----
16              
17             # Math::Polynomial::ModInt=ARRAY(...)
18              
19             # .............. index .............. # .......... value ..........
20              
21 3     3   14 use constant _OFFSET => Math::Polynomial::_NFIELDS;
  3         4  
  3         178  
22 3     3   24 use constant _F_INDEX => _OFFSET + 0; # ordinal number i, 0 <= i
  3         7  
  3         122  
23 3     3   15 use constant _NFIELDS => _OFFSET + 1;
  3         5  
  3         289  
24              
25             # ----- class data -----
26              
27             BEGIN {
28 3     3   17 require Exporter;
29 3         92 our @ISA = qw(Math::Polynomial Exporter);
30 3         9 our @EXPORT_OK = qw(modpoly);
31 3         18 our $VERSION = '0.005';
32 3         4899 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 _nonprime_modulus {
53 3     3   406 croak 'modulus not prime';
54             }
55              
56             # event catcher
57             sub _no_inverse {
58 1     1   341 croak 'undefined inverse';
59             }
60              
61             # constructor diagnostic
62             sub _modulus_too_small {
63 2     2   242 croak 'modulus must be greater than one';
64             }
65              
66             # wrapper for modular inverse to bail out early if not in a field
67             sub _inverse {
68 2     2   5 my ($element) = @_;
69 2         11 my $trap = UndefinedResult->trap(\&_no_inverse);
70 2         49 return $element->inverse;
71             }
72              
73             # Does a set of coefficient vectors have a zero linear combination?
74             # We transform and include them one by one into a matrix in echelon form.
75             # Argument is an iterator so we can short-cut generating superfluous vectors.
76             # Supplied vectors may be modified.
77             sub _linearly_dependent {
78 7     7   13 my ($it) = @_;
79 7         16 my $trap = UndefinedResult->trap(\&_nonprime_modulus);
80 7         117 my @ech = ();
81 7         13 while (defined(my $vec = $it->())) {
82 22         612 my $ex = 0;
83 22         41 for (; $ex < @ech; ++$ex) {
84 25         100 my $evec = $ech[$ex];
85 25 100       26 last if @{$evec} < @{$vec};
  25         36  
  25         68  
86 20 100       30 if (@{$evec} == @{$vec}) {
  20         27  
  20         37  
87 16         18 my $i = $#{$vec};
  16         23  
88 16 100       32 return 1 if !$i;
89 15         16 my $w = pop(@{$vec}) / $evec->[$i];
  15         32  
90 14         211 while ($i-- > 0) {
91 42         524 $vec->[$i] -= $evec->[$i] * $w;
92             }
93 14   100     290 while (@{$vec} && !$vec->[-1]) {
  25         69  
94 11         71 pop(@{$vec});
  11         13  
95             }
96             }
97             }
98 20 100       65 return 1 if !@{$vec};
  20         37  
99 19         49 splice @ech, $ex, 0, $vec;
100             }
101 4         14 return 0;
102             }
103              
104             # ----- private methods -----
105              
106             # wrapper to decorate stringified polynomial
107             sub _wrap {
108 478     478   12314 my ($this, $text) = @_;
109 478         725 my $modulus = $this->modulus;
110 478         3412 return "$text (mod $modulus)";
111             }
112              
113             # ----- protected methods -----
114              
115             # constructor with index argument
116             sub _xnew {
117 148     148   196 my $this = shift;
118 148         177 my $index = shift;
119 148         240 my $poly = $this->new(@_);
120 148         7689 $poly->[_F_INDEX] = $index;
121 148         466 return $poly;
122             }
123              
124             # ----- overridden public methods -----
125              
126             sub new {
127 500     500 1 22134 my $this = shift;
128 500 100 100     846 if (!@_ && !ref $this) {
129 1         119 croak 'insufficient arguments';
130             }
131 499 100       714 if (grep {$_->is_undefined} @_) {
  1639         5422  
132 2         11 _nonprime_modulus();
133             }
134 497 100       2278 if (grep {$_->modulus <= 1} @_) {
  1636         4001  
135 1         5 _modulus_too_small();
136             }
137 496         1923 return $this->SUPER::new(@_);
138             }
139              
140             sub string_config {
141 963     963 1 43906 my $this = shift;
142 963 100       2112 return $this->SUPER::string_config(@_) if ref $this;
143 482 100       724 ($default_string_config) = @_ if @_;
144 482         2768 return $default_string_config;
145             }
146              
147             sub is_equal {
148 10     10 1 826 my ($this, $that) = @_;
149 10         19 my $i = $this->degree;
150 10   100     45 my $eq = $this->modulus == $that->modulus && $i == $that->degree;
151 10   100     104 while ($eq && 0 <= $i) {
152 22         35 $eq = $this->coeff($i)->residue == $that->coeff($i)->residue;
153 22         314 --$i;
154             }
155 10         37 return $eq;
156             }
157              
158             sub is_unequal {
159 5     5 1 9 my ($this, $that) = @_;
160 5         10 my $i = $this->degree;
161 5   100     21 my $eq = $this->modulus == $that->modulus && $i == $that->degree;
162 5   100     48 while ($eq && 0 <= $i) {
163 10         16 $eq = $this->coeff($i)->residue == $that->coeff($i)->residue;
164 10         144 --$i;
165             }
166 5         17 return !$eq;
167             }
168              
169             sub is_monic {
170 55     55 1 2434 my ($this) = @_;
171 55         88 my $degree = $this->degree;
172 55   100     259 return 0 <= $degree && $this->coeff($degree)->residue == 1;
173             }
174              
175             sub monize {
176 4     4 1 18 my ($this) = @_;
177 4         9 my $degree = $this->degree;
178 4 100       22 return $this if $degree < 0;
179 3         8 my $leader = $this->coeff($degree);
180 3 100       25 return $this if $leader->residue == 1;
181 2         9 return $this->mul_const(_inverse($leader));
182             }
183              
184             # ----- public subroutine -----
185              
186 146     146 1 6995 sub modpoly { __PACKAGE__->from_index(@_) }
187              
188             # ----- class-specific public methods -----
189              
190             sub from_index {
191 150     150 1 2641 my ($this, $index, $modulus) = @_;
192 150         171 my $zero;
193 150 100       245 if (defined $modulus) {
    100          
194 148 100       260 $modulus > 1 || _modulus_too_small();
195 147         263 $zero = Math::ModInt::mod(0, $modulus);
196             }
197             elsif (ref $this) {
198 1         4 $zero = $this->coeff_zero;
199 1         6 $modulus = $zero->modulus;
200             }
201             else {
202 1         196 croak('usage error: modulus parameter missing');
203             }
204 148         13832 my @coeff = ();
205 148         179 my $q = $index;
206 148         260 while ($q > 0) {
207 353         586 ($q, my $r) = $zero->new2($q);
208 353         5804 push @coeff, $r;
209             }
210 148 100       342 return $this->_xnew(@coeff? ($index, @coeff): (0, $zero));
211             }
212              
213             sub from_int_poly {
214 3     3 1 1139 my ($this, $poly, $modulus) = @_;
215 3 100       10 if (!defined $modulus) {
216 2 100       5 if (!ref $this) {
217 1         126 croak('usage error: modulus parameter missing');
218             }
219 1         3 $modulus = $this->modulus;
220             }
221 2         10 my @coeff = map { Math::ModInt::mod($_, $modulus) } $poly->coefficients;
  12         244  
222 2         42 return $this->new(@coeff);
223             }
224              
225             sub modulus {
226 1264     1264 1 3884 my ($this) = @_;
227 1264         1792 return $this->coeff_zero->modulus;
228             }
229              
230             sub index {
231 6     6 1 590 my ($this) = @_;
232 6         12 my $base = $this->modulus;
233 6         29 my $index = $this->[_F_INDEX];
234 6 100       12 if (!defined $index) {
235 2         9 $index = Math::BigInt->new;
236 2         158 foreach my $c (reverse $this->coeff) {
237 12         2893 $index->bmul($base)->badd($c->residue);
238             }
239 2         537 $this->[_F_INDEX] = $index;
240             }
241 6         18 return $index;
242             }
243              
244 181     181 1 3320 sub number_of_terms { scalar grep { $_->is_not_zero } $_[0]->coeff }
  642         3717  
245              
246 1     1 1 435 sub lift { $ipol->new(map { $_->residue } $_[0]->coefficients) }
  6         34  
247              
248 2     2 1 3504 sub centerlift { $ipol->new(map { $_->centered_residue } $_[0]->coefficients) }
  8         56  
249              
250             sub lambda_reduce {
251 8     8 1 1477 my ($this, $lambda) = @_;
252 8         16 my $degree = $this->degree;
253 8 100       38 return $this if $degree <= $lambda;
254 7         10 my @coeff = map { $this->coeff($_) } 0 .. $lambda;
  22         105  
255 7         55 for (my $i = 1, my $n = $lambda+1; $n <= $degree; ++$i, ++$n) {
256 16         28 $coeff[$i] += $this->coeff($n);
257 16 100       276 $i = 0 if $i == $lambda;
258             }
259 7         14 return $this->new(@coeff);
260             }
261              
262             sub first_root {
263 16     16 1 38 my ($this) = @_;
264 16         28 my $zero = $this->coeff_zero;
265 16         48 my $lsc = $this->coeff(0);
266 16 100       112 return $zero if $lsc->is_zero;
267 15 100       96 if ($this->degree == 1) {
268 5         22 my $mscr = $this->coeff(1)->centered_residue;
269 5 100       79 return -$lsc if $mscr == 1;
270 4 100       12 return $lsc if $mscr == -1;
271             }
272 12         51 foreach my $n (1 .. $zero->modulus - 1) {
273 66         6546 my $root = $zero->new($n);
274 66 100       564 return $root if $this->evaluate($root)->is_zero;
275             }
276 9         701 return undef;
277             }
278              
279             # implementation restriction: defined for prime moduli only
280             sub is_irreducible {
281 16     16 1 524 my ($this) = @_;
282 16         30 my $n = $this->degree;
283 16 100       82 return 0 if $n <= 0;
284 15 100       28 return 1 if $n == 1;
285 14 100       25 return 0 if !$this->coeff(0);
286 13 100       255 return 0 if $this->gcd($this->differentiate)->degree > 0;
287 11         241 my $p = $this->modulus;
288             # optimization: O(p) zero search only for small p or very large n
289 11 100 100     65 if ($p <= 43 || log($p - 20) <= $n * 0.24 + 2.68) {
290 10 100 100     40 my $rp = 2 < $p && $p <= $n? $this->lambda_reduce($p-1): $this;
291 10 100       186 return 0 if defined $rp->first_root;
292 8 100       24 return 1 if $n <= 3;
293             }
294             # Berlekamp rank < $n - 1?
295 7         17 my $xp = $this->exp_mod($p);
296 7         201 my $aj = $xp;
297 7         16 my $bj = $this->monomial(1);
298 7         148 my $j = 0;
299             return 0 if _linearly_dependent(
300             sub {
301 26 100   26   52 return undef if $j >= $n - 1;
302 22 100       37 if ($j++) {
303 15         27 $aj = $aj * $xp % $this;
304 15         421 $bj <<= 1;
305             }
306 22         314 return [($aj - $bj)->coeff];
307             }
308 7 100       30 );
309 4         72 return 1;
310             }
311              
312             1;
313              
314             __END__