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   164054 use strict;
  4         28  
  4         88  
3 4     4   17 use warnings;
  4         4  
  4         125  
4             { our $VERSION = '0.004'; }
5              
6 4     4   1645 use Moo;
  4         35228  
  4         14  
7 4     4   6062 use Ouch;
  4         12335  
  4         13  
8 4     4   1522 use Math::GF::Zn;
  4         10  
  4         174  
9              
10 4     4   21 use constant MARGIN => 1.1;
  4         6  
  4         1134  
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 2269 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 1900 my $self = shift;
28 4         23 my $eclass = $self->element_class;
29 4         11 my $order = $self->order;
30 4         14 map { $eclass->new(field => $self, v => $_) } 0 .. ($order - 1);
  14         308  
31             } ## end sub all
32              
33             # import a handy factory method into caller's package
34             sub import_builder {
35 3     3 1 508 my ($package, $order) = splice @_, 0, 2;
36 3 50 66     22 my %args = (@_ && ref($_[0]) eq 'HASH') ? %{$_[0]} : @_;
  0         0  
37              
38 3         70 my $field = $package->new(order => $order);
39 3     15   59 my $builder = sub { return $field->e(@_) };
  15         2129  
40 3   50     19 my $callpkg = caller($args{level} // 0);
41             my $name = $args{name} // (
42 3 50 66     28 $field->order_is_prime
43             ? "GF_$order"
44             : join('_', 'GF', $field->p, $field->n)
45             );
46 4     4   31 no strict 'refs';
  4         19  
  4         4716  
47 3         5 *{$callpkg . '::' . $name} = $builder;
  3         15  
48 3         11 return;
49             } ## end sub import_builder
50              
51             # factory method to create "e"lements of the field
52             sub e {
53 54     54 1 1130 my $self = shift;
54 54         112 my $ec = $self->element_class;
55 54 100       864 return $ec->new(field => $self, v => $_[0]) unless wantarray;
56 6         13 return map { $ec->new(field => $self, v => $_) } @_;
  6         104  
57             }
58              
59             # neutral element for "*" operation
60 13     13 1 884 sub multiplicative_neutral { return $_[0]->e(1) }
61              
62             sub BUILDARGS {
63 9     9 1 5307 my ($class, %args) = @_;
64              
65 9 50       32 ouch 500, 'missing order' unless exists $args{order};
66 9         19 my $order = $args{order};
67 9 50       21 ouch 500, 'undefined order' unless defined $order;
68 9 50       43 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         24 my ($p, $n) = __prime_power_decomposition($order);
72 9 50       22 ouch 500, 'order MUST be a power of a prime'
73             unless defined $p;
74 9         19 $args{p} = $p;
75 9         15 $args{n} = $n;
76 9         20 $args{order_is_prime} = ($n == 1);
77 9 100       21 if ($n == 1) {
78 6         11 $args{order_is_prime} = 1;
79 6         12 $args{element_class} = 'Math::GF::Zn';
80 6         16 delete @args{qw< sum_table prod_table >};
81             }
82             else {
83 3         6 $args{order_is_prime} = 0;
84 3         7 $args{element_class} = 'Math::GF::Extension';
85 3         10 @args{qw< sum_table prod_table >} = __tables($order);
86 3         1009 require Math::GF::Extension;
87             }
88              
89 9         221 return {%args};
90             } ## end sub BUILDARGS
91              
92             sub __tables {
93 3     3   8 my $order = shift;
94              
95             # Get the basic subfield
96 3         8 my ($p, $n) = __prime_power_decomposition($order);
97 3         79 my $Zp = Math::GF->new(order => $p);
98 3         64 my @Zp_all = $Zp->all;
99 3         36 my ($zero, $one) = ($Zp->additive_neutral, $Zp->multiplicative_neutral);
100              
101 3         37 my $pirr = __get_irreducible_polynomial($Zp, $n);
102 3         34 my $polys = __generate_polynomials($Zp, $n);
103 3         12 my %id_for = map {; "$polys->[$_]" => $_ } 0 .. $#$polys;
  16         184  
104              
105 3         48 my (@sum, @prod);
106 3         12 for my $i (0 .. $#$polys) {
107 16         200 my $I = $polys->[$i];
108 16         27 push @sum, \my @ts;
109 16         23 push @prod, \my @tp;
110 16         40 for my $j (0 .. $i) {
111 56         514 my $J = $polys->[$j];
112 56         102 my $sum = ($I + $J);
113 56         443 push @ts, $id_for{"$sum"};
114 56         660 my $prod = ($I * $J) % $pirr;
115 56         768 push @tp, $id_for{"$prod"};
116             }
117             }
118              
119 3         100 return (\@sum, \@prod);
120             }
121              
122             sub __generate_polynomials {
123 3     3   7 my ($field, $degree) = @_;
124 3 50       10 ouch 500, 'irreducible polynomial search only over Zn field'
125             unless $field->order_is_prime;
126 3         8 my $zero = $field->additive_neutral;
127 3         32 my $one = $field->multiplicative_neutral;
128              
129 3         26 my @coeffs = ($zero) x ($degree + 1); # last one as a flag
130 3         6 my @retval;
131 3         8 while ($coeffs[-1] == $zero) {
132 16         42 push @retval, Math::Polynomial->new(@coeffs);
133 16         81 for (@coeffs) {
134 29         49 $_ = $_ + $one;
135 29 100       203 last unless $_ == $zero;
136             }
137             }
138 3         19 return \@retval;
139             }
140              
141             sub __get_irreducible_polynomial {
142 3     3   8 my ($field, $degree) = @_;
143 3 50       24 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         34 my $one = $field->multiplicative_neutral;
148 3         1406 require Math::Polynomial;
149 3         14001 my @coeffs = ($one, (($zero) x ($degree - 1)), $one);
150 3         20 while ($coeffs[-1] == $one) {
151 9         33 my $poly = Math::Polynomial->new(@coeffs);
152 9 100       51 return $poly if __rabin_irreducibility_test($poly);
153 6         56 for (@coeffs) {
154 9         20 $_ = $_ + $one;
155 9 100       70 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   13 my $f = shift;
174 9         21 my $n = $f->degree;
175 9         102 my $one = $f->coeff_one;
176 9         37 my $pone = Math::Polynomial->monomial(0, $one);
177 9         62 my $x = Math::Polynomial->monomial(1, $one);
178 9         161 my $q = $one->n;
179 9         65 my $ps = __prime_divisors_of($n);
180              
181 9         17 for my $pi (@$ps) {
182 9         16 my $ni = $n / $pi;
183 9         13 my $qni = $q**$ni;
184 9         23 my $h = (Math::Polynomial->monomial($qni, $one) - $x) % $f;
185 9         153 my $g = $h->gcd($f, 'mod');
186             #return if $g != $pone;
187 9 100       297 return if $g->degree > 1;
188             } ## end for my $pi (@$ps)
189 6         69 my $t = (Math::Polynomial->monomial($q**$n, $one) - $x) % $f;
190 6         59 return $t->degree == -1;
191             } ## end sub rabin_irreducibility_test
192              
193             sub __prime_power_decomposition {
194 12     12   22 my $x = shift;
195 12 50       28 return if $x < 2;
196 12 100       35 return ($x, 1) if $x < 4;
197              
198 7         17 my $p = __prime_divisors_of($x, 'first only please');
199 7 100       20 return ($x, 1) if $x == $p; # $x is prime
200              
201 6         12 my $e = 0;
202 6         14 while ($x > 1) {
203 14 50       32 return if $x % $p; # not the only divisor!
204 14         26 $x /= $p;
205 14         30 ++$e;
206             }
207 6         17 return ($p, $e);
208             } ## end sub __prime_power_decomposition
209              
210             sub __prime_divisors_of {
211 16     16   33 my ($n, $first_only) = @_;
212 16         25 my @retval;
213              
214 16 50       30 return if $n < 2;
215              
216 16         35 for my $p (2, 3) { # handle cases for 2 and 3 first
217 26 100       55 next if $n % $p;
218 15 100       39 return $p if $first_only;
219 9         18 push @retval, $p;
220 9         37 $n /= $p until $n % $p;
221             }
222              
223 10         15 my $p = 5; # tentative divisor, will increase through iterations
224 10         24 my $top = int(sqrt($n) + MARGIN); # top attempt for divisor
225 10         14 my $d = 2; # increase for $p, alternates between 4 and 2
226 10         23 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       20 return $n if $first_only; # always in this case
239 9 50       14 push @retval, $n if $n > 1;
240              
241 9         18 return \@retval;
242             } ## end sub prime_divisors_of
243              
244             1;