File Coverage

blib/lib/Math/GF.pm
Criterion Covered Total %
statement 148 164 90.2
branch 34 54 62.9
condition 5 8 62.5
subroutine 20 21 95.2
pod 6 6 100.0
total 213 253 84.1


line stmt bran cond sub pod time code
1             package Math::GF;
2 4     4   256680 use strict;
  4         38  
  4         130  
3 4     4   27 use warnings;
  4         8  
  4         196  
4             { our $VERSION = '0.001001'; }
5              
6 4     4   2271 use Moo;
  4         52255  
  4         22  
7 4     4   8822 use Ouch;
  4         18458  
  4         20  
8 4     4   2106 use Math::GF::Zn;
  4         18  
  4         158  
9              
10 4     4   32 use constant MARGIN => 1.1;
  4         9  
  4         1584  
11              
12             has order => (is => 'ro');
13             has p => (is => 'ro');
14             has n => (is => 'ro');
15             has order_is_prime => (is => 'ro');
16             has element_class => (is => 'ro');
17              
18             # The following are used only for extension fields
19             has sum_table => (is => 'ro');
20             has prod_table => (is => 'ro');
21              
22             # neutral element for "+" operation
23 13     13 1 2443 sub additive_neutral { return $_[0]->e(0) }
24              
25             # factory method to create "all" elements in the field
26             sub all {
27 4     4 1 2101 my $self = shift;
28 4         20 my $eclass = $self->element_class;
29 4         12 my $order = $self->order;
30 4         14 map { $eclass->new(field => $self, v => $_) } 0 .. ($order - 1);
  14         363  
31             } ## end sub all
32              
33             # import a handy factory method into caller's package
34             sub import_builder {
35 3     3 1 529 my ($package, $order) = splice @_, 0, 2;
36 3 50 66     28 my %args = (@_ && ref($_[0]) eq 'HASH') ? %{$_[0]} : @_;
  0         0  
37              
38 3         103 my $field = $package->new(order => $order);
39 3     15   81 my $builder = sub { return $field->e(@_) };
  15         2932  
40 3   50     26 my $callpkg = caller($args{level} // 0);
41             my $name = $args{name} // (
42 3 50 66     35 $field->order_is_prime
43             ? "GF_$order"
44             : join('_', 'GF', $field->p, $field->n)
45             );
46 4     4   42 no strict 'refs';
  4         22  
  4         6724  
47 3         9 *{$callpkg . '::' . $name} = $builder;
  3         22  
48 3         16 return;
49             } ## end sub import_builder
50              
51             # factory method to create "e"lements of the field
52             sub e {
53 54     54 1 1586 my $self = shift;
54 54         148 my $ec = $self->element_class;
55 54 100       1178 return $ec->new(field => $self, v => $_[0]) unless wantarray;
56 6         15 return map { $ec->new(field => $self, v => $_) } @_;
  6         148  
57             }
58              
59             # neutral element for "*" operation
60 13     13 1 970 sub multiplicative_neutral { return $_[0]->e(1) }
61              
62             sub BUILDARGS {
63 9     9 1 5875 my ($class, %args) = @_;
64              
65 9 50       41 ouch 500, 'missing order' unless exists $args{order};
66 9         25 my $order = $args{order};
67 9 50       31 ouch 500, 'undefined order' unless defined $order;
68 9 50       59 ouch 500, 'order MUST be integer and greater than 1'
69             unless $order =~ m{\A(?: [2-9] | [1-9]\d+)\z}mxs;
70              
71 9         41 my ($p, $n) = __prime_power_decomposition($order);
72 9 50       30 ouch 500, 'order MUST be a power of a prime'
73             unless defined $p;
74 9         26 $args{p} = $p;
75 9         21 $args{n} = $n;
76 9         24 $args{order_is_prime} = ($n == 1);
77 9 100       33 if ($n == 1) {
78 6         16 $args{order_is_prime} = 1;
79 6         14 $args{element_class} = 'Math::GF::Zn';
80 6         22 delete @args{qw< sum_table prod_table >};
81             }
82             else {
83 3         7 $args{order_is_prime} = 0;
84 3         9 $args{element_class} = 'Math::GF::Extension';
85 3         10 @args{qw< sum_table prod_table >} = __tables($order);
86 3         1130 require Math::GF::Extension;
87             }
88              
89 9         299 return {%args};
90             } ## end sub BUILDARGS
91              
92             sub __tables {
93 3     3   7 my $order = shift;
94              
95             # Get the basic subfield
96 3         8 my ($p, $n) = __prime_power_decomposition($order);
97 3         88 my $Zp = Math::GF->new(order => $p);
98 3         65 my @Zp_all = $Zp->all;
99 3         47 my ($zero, $one) = ($Zp->additive_neutral, $Zp->multiplicative_neutral);
100              
101 3         42 my $pirr = __get_irreducible_polynomial($Zp, $n);
102 3         58 my $polys = __generate_polynomials($Zp, $n);
103 3         15 my %id_for = map {; "$polys->[$_]" => $_ } 0 .. $#$polys;
  16         243  
104              
105 3         81 my (@sum, @prod);
106 3         15 for my $i (0 .. $#$polys) {
107 16         255 my $I = $polys->[$i];
108 16         40 push @sum, \my @ts;
109 16         35 push @prod, \my @tp;
110 16         41 for my $j (0 .. $i) {
111 56         697 my $J = $polys->[$j];
112 56         145 my $sum = ($I + $J) % $pirr;
113 56         1398 push @ts, $id_for{"$sum"};
114 56         895 my $prod = ($I * $J) % $pirr;
115 56         1039 push @tp, $id_for{"$prod"};
116             }
117             }
118              
119 3         133 return (\@sum, \@prod);
120             }
121              
122             sub __generate_polynomials {
123 3     3   9 my ($field, $degree) = @_;
124 3 50       16 ouch 500, 'irreducible polynomial search only over Zn field'
125             unless $field->order_is_prime;
126 3         13 my $zero = $field->additive_neutral;
127 3         40 my $one = $field->multiplicative_neutral;
128              
129 3         40 my @coeffs = ($zero) x ($degree + 1); # last one as a flag
130 3         7 my @retval;
131 3         13 while ($coeffs[-1] == $zero) {
132 16         62 push @retval, Math::Polynomial->new(@coeffs);
133 16         137 for (@coeffs) {
134 29         86 $_ = $_ + $one;
135 29 100       358 last unless $_ == $zero;
136             }
137             }
138 3         25 return \@retval;
139             }
140              
141             sub __get_irreducible_polynomial {
142 3     3   10 my ($field, $degree) = @_;
143 3 50       15 ouch 500, 'irreducible polynomial search only over Zn field'
144             unless $field->order_is_prime;
145              
146 3         9 my $zero = $field->additive_neutral;
147 3         37 my $one = $field->multiplicative_neutral;
148 3         1588 require Math::Polynomial;
149 3         18111 my @coeffs = ($one, (($zero) x ($degree - 1)), $one);
150 3         19 while ($coeffs[-1] == $one) {
151 9         47 my $poly = Math::Polynomial->new(@coeffs);
152 9 100       88 return $poly if __rabin_irreducibility_test($poly);
153 6         104 for (@coeffs) {
154 9         39 $_ = $_ + $one;
155 9 100       133 last unless $_ == $zero; # wrapped up
156             }
157             }
158 0         0 ouch 500, "no monic irreducibile polynomial!"; # never happens
159             }
160              
161             sub __to_poly {
162 0     0   0 my ($x, $n) = @_;
163 0         0 my @coeffs;
164 0         0 while ($x) {
165 0         0 push @coeffs, $x % $n;
166 0         0 $x = ($x - $coeffs[-1]) / $n;
167             }
168 0 0       0 push @coeffs, 0 unless @coeffs;
169 0         0 return Z_poly($n, @coeffs);
170             }
171              
172             sub __rabin_irreducibility_test {
173 9     9   17 my $f = shift;
174 9         33 my $n = $f->degree;
175 9         141 my $one = $f->coeff_one;
176 9         55 my $pone = Math::Polynomial->monomial(0, $one);
177 9         129 my $x = Math::Polynomial->monomial(1, $one);
178 9         285 my $q = $one->n;
179 9         103 my $ps = __prime_divisors_of($n);
180              
181 9         27 for my $pi (@$ps) {
182 9         22 my $ni = $n / $pi;
183 9         23 my $qni = $q**$ni;
184 9         40 my $h = (Math::Polynomial->monomial($qni, $one) - $x) % $f;
185 9         202 my $g = $h->gcd($f, 'mod');
186             #return if $g != $pone;
187 9 100       486 return if $g->degree > 1;
188             } ## end for my $pi (@$ps)
189 6         110 my $t = (Math::Polynomial->monomial($q**$n, $one) - $x) % $f;
190 6         149 return $t->degree == -1;
191             } ## end sub rabin_irreducibility_test
192              
193             sub __prime_power_decomposition {
194 12     12   29 my $x = shift;
195 12 50       36 return if $x < 2;
196 12 100       40 return ($x, 1) if $x < 4;
197              
198 7         27 my $p = __prime_divisors_of($x, 'first only please');
199 7 100       25 return ($x, 1) if $x == $p; # $x is prime
200              
201 6         10 my $e = 0;
202 6         16 while ($x > 1) {
203 14 50       30 return if $x % $p; # not the only divisor!
204 14         25 $x /= $p;
205 14         30 ++$e;
206             }
207 6         19 return ($p, $e);
208             } ## end sub __prime_power_decomposition
209              
210             sub __prime_divisors_of {
211 16     16   46 my ($n, $first_only) = @_;
212 16         34 my @retval;
213              
214 16 50       51 return if $n < 2;
215              
216 16         39 for my $p (2, 3) { # handle cases for 2 and 3 first
217 26 100       89 next if $n % $p;
218 15 100       47 return $p if $first_only;
219 9         30 push @retval, $p;
220 9         50 $n /= $p until $n % $p;
221             }
222              
223 10         25 my $p = 5; # tentative divisor, will increase through iterations
224 10         33 my $top = int(sqrt($n) + MARGIN); # top attempt for divisor
225 10         24 my $d = 2; # increase for $p, alternates between 4 and 2
226 10         48 while ($p <= $top) {
227 0 0       0 if ($n % $p == 0) {
228 0 0       0 return $p if $first_only;
229 0         0 unshift @retval, $p;
230 0         0 $n /= $p until $n % $p;
231 0         0 $top = int(sqrt($n) + MARGIN);
232             }
233 0         0 $p += $d;
234 0 0       0 $d = ($d == 2) ? 4 : 2;
235             } ## end while ($n > 1)
236              
237             # exited with $n left as a prime... maybe
238 10 100       28 return $n if $first_only; # always in this case
239 9 50       29 push @retval, $n if $n > 1;
240              
241 9         31 return \@retval;
242             } ## end sub prime_divisors_of
243              
244             1;