File Coverage

blib/lib/Math/Assistant.pm
Criterion Covered Total %
statement 184 195 94.3
branch 59 82 71.9
condition 14 23 60.8
subroutine 12 12 100.0
pod 5 5 100.0
total 274 317 86.4


line stmt bran cond sub pod time code
1             package Math::Assistant;
2              
3 1     1   48065 use 5.008004;
  1         4  
  1         51  
4 1     1   7 use strict;
  1         2  
  1         33  
5 1     1   5 use warnings;
  1         6  
  1         31  
6              
7 1     1   12 use Carp;
  1         2  
  1         101  
8 1     1   1675 use Math::BigInt qw( bgcd );
  1         28417  
  1         5  
9              
10             require Math::BigFloat;
11             require Exporter;
12              
13             our @ISA = qw(Exporter);
14              
15             our %EXPORT_TAGS = ( 'all' => [ qw(
16             Rank Det Solve_Det Gaussian_elimination test_matrix
17             ) ],
18             'algebra' => [ qw( Rank Det Solve_Det ) ],
19             );
20              
21             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
22              
23             our @EXPORT = qw( );
24              
25             our $VERSION = '0.05';
26              
27 1     1   20098 use base qw(Exporter);
  1         2  
  1         2200  
28              
29              
30             # Rank of any integer matrix
31             # input:
32             # $Dimention
33             # return:
34             # $rank (order)
35             sub Rank{
36 4     4 1 31 my $M = &Gaussian_elimination; # triangular matrix
37 4         8 my $rank = 0;
38              
39 4         10 for( @$M ){ # rows
40 24         314 for( @{$_} ){ # element in row
  24         43  
41 90 100       235 $rank++, last if $_
42             }
43             }
44 4         374 $rank;
45             }
46              
47              
48             # Method of Gaussian elimination
49             # input:
50             # $Dimention
51             # return:
52             # $elimination_Dimention
53             sub Gaussian_elimination{
54 4     4 1 7 my $M_ini = shift;
55              
56 4         16 my $t = &test_matrix( $M_ini );
57 4 50       18 if( $t > 3 ){
    50          
58 0         0 croak("Use of uninitialized value in matrix");
59              
60             }elsif( $t > 1 ){
61 0         0 croak("Bad matrix");
62             }
63              
64 4         6 my $rows = $#{$M_ini}; # number of rows
  4         8  
65 4         6 my $cols = $#{$M_ini->[0]}; # number of columns
  4         9  
66              
67 4         7 my %zr; # for rows with 0
68             my %zc; # for columns with 0
69              
70 0         0 my $M; # copy
71             # search of rows with 0
72 4         13 for( my $i = 0; $i <= $rows; $i++ ){
73 24         48 for( my $j = 0; $j <= $cols; $j++ ){
74 154         839 $M->[$i][$j] = $M_ini->[$i][$j];
75 154 100       435 unless($M->[$i][$j]){ # zero element
76 4         10 $zr{$i}++;
77 4         13 $zc{$j}++;
78             }
79             }
80             }
81              
82             # Check Float (Fraction)
83 4         9 for my $i ( @$M ){
84 24         50 my $max_frac = &_max_len_frac($i, 0);
85              
86 24 50       145 if($max_frac){
87 0         0 $_ *= 10**$max_frac for @$i;
88             }
89             }
90              
91 4 100       22 if(keys %zr){ # yes, zero rows (columns)
92              
93             # I supplement indexes of nonzero rows
94 2         8 for( my $i = 0; $i <= $rows; $i++ ){
95 9         27 $zr{$i} += 0;
96             }
97              
98 2         4 my $R; # temp copy of matrix M
99 2         3 my $v = 0;
100             # replacement of rows
101 2         17 for my $i ( sort { $zr{$a} <=> $zr{$b} } keys %zr ){
  13         25  
102 9         20 for( my $j = 0; $j <= $cols; $j++ ){
103 41         312 $R->[$v][$j] = $M->[$i][$j];
104             }
105 9         17 $v++;
106             }
107              
108             # I supplement indexes of non zero columns
109 2         173 for( my $j = 0; $j <= $cols; $j++ ){
110 9         81 $zc{$j} += 0;
111             }
112              
113 2         9 $v = 0;
114             # replacement of columns
115 2         8 for my $j ( sort { $zc{$b} <=> $zc{$a} } keys %zc ){
  14         21  
116 9         19 for( my $i = 0; $i <= $rows; $i++ ){
117 41         278 $M->[$i][$v] = $R->[$i][$j];
118             }
119 9         16 $v++;
120             }
121             }
122              
123 4         15 undef %zr;
124 4         7 undef %zc;
125              
126 4         140 for(my $n = 0; $n < $rows; $n++){
127 20 50       6136 last if $n > $cols; # other zero rows
128              
129             # Replacement of zero diagonal element
130 20         27 my $s = 0;
131             M_Gauss1:
132 20   100     71 while( $M->[$n][$n] == 0 && $s < $rows - $n ){
133 2         3 $s++;
134 2         6 for( my $j = $n + 1; $j <= $cols; $j++ ){
135              
136 2 100       7 if( $M->[$n][$j] ){ # no zero element
137             # shift of columns $n <-> $j
138 1         6 for( my $i = 0; $i <= $rows; $i++ ){
139 5         17 ($M->[$i][$j], $M->[$i][$n]) = ($M->[$i][$n], $M->[$i][$j]);
140             }
141 1         3 last M_Gauss1;
142             }
143             }
144              
145             # all zero elements in row
146 1         5 for( my $j = $n; $j <= $cols; $j++ ){
147             # shift of rows $n <-> $n+1
148 2         12 ($M->[$n][$j], $M->[$n+$s][$j]) = ($M->[$n+$s][$j], $M->[$n][$j]);
149             }
150             }
151              
152 20 100       45 last unless $M->[$n][$n]; # zero elements of rows
153              
154 19         171 for( my $i = $n+1; $i <= $rows; $i++ ){
155              
156             # Divisibility check totally
157 64         64 my($k,$d);
158 64         173 my $b = $M->[$i][$n] / $M->[$n][$n];
159 64 100       117 if($b == int($b)){
160 31         260 $k = -$b;
161 31         39 $d = 1;
162             }else{
163 33         43 $k = -$M->[$i][$n];
164 33         50 $d = $M->[$n][$n];
165             }
166              
167             # column (element in row)
168 64         128 for( my $j = $n; $j <= $cols; $j++ ){
169 338         1081 $M->[$i][$j] = $M->[$i][$j]*$d + $k*$M->[$n][$j];
170             }
171             }
172              
173 19         39 my $gcd = bgcd( @{$M->[$n]}[$n..$cols] );
  19         179  
174 19 100       9944 if($gcd > 1){
175 11         1723 $_ = $_ / $gcd for @{$M->[$n]}[$n..$cols];
  11         48  
176             }
177             }
178 4         1477 $M;
179             }
180              
181              
182             # Interger determinant for quadratic matrix
183             # input:
184             # $Dimention
185             # facultative parameter
186             # return:
187             # determinant
188             # undef
189             sub Det{
190 17     17 1 1552 my( $M_ini, $opt ) = @_;
191              
192 17         24 my $dm = $#{ $M_ini }; # dimension of matrix
  17         30  
193              
194 17 50       50 return $M_ini->[0][0] if $dm < 1; # dim matrix = 1
195              
196             # Check Float (Fraction)
197 17         24 my $fraction = 0;
198 17 100 66     227 unless( exists $opt->{'int'} && $opt->{'int'} ){
199 14         28 for my $i ( @$M_ini ){
200 80         147 $fraction = &_max_len_frac($i, $fraction);
201             }
202             }
203              
204 24         34 my $M = $fraction ? [ map{ [ map{ $_ * 10**$fraction } @$_ ] } @$M_ini ] :
  96         404  
  75         808  
205 17 100       67 [ map{ [ @$_ ] } @$M_ini ]; # copy
206              
207 17         40 my $exch = 0; # number of permutations
208 17         98 my $denom = 1;
209              
210 17         59 for( my $i = 0; $i < $dm; $i++ ){ # take all the matrix rows except 1st
211 82         138 my $minV = abs( $M->[$i][$i] );
212              
213 82 100 66     353 if( ! $minV && $i < $dm - 1 ){
214 1         2 my $minN = $i;
215              
216             # Search of row with abs minimal element on the diagonal
217 1         5 for( my $j = $i + 1; $j <= $dm; $j++ ){
218 3         5 my $v = abs( $M->[$j][$i] );
219              
220 3 100 100     27 if( $v && ($v < $minV || ! $minV) ){
      33        
221 2         3 $minN = $j;
222 2         5 $minV = $v;
223             }
224             }
225              
226 1 50       4 return 0 unless $minV; # determinant = 0
227              
228 1         3 ( $M->[$i], $M->[$minN] ) = ( $M->[$minN], $M->[$i] );
229 1         2 $exch++;
230             }
231              
232 82         106 my $v1 = $M->[$i][$i];
233              
234 82         186 for( my $j = $i + 1; $j <= $dm; $j++ ){
235 259         419 my $v2 = $M->[$j][$i];
236              
237 259         776 for( my $k = $i + 1; $k <= $dm; $k++ ){
238 1057         4935 $M->[$j][$k] = ( $M->[$j][$k] * $v1 - $M->[$i][$k] * $v2 ) / $denom;
239             }
240             }
241 82         243 $denom = $v1;
242             }
243              
244 17         46 for( $M->[$dm][$dm] ){
245 17 100       44 if( $_ < 0 ){
246 13         23 $_ = abs;
247 13         18 $exch++;
248             }
249 17 50       56 $_ = 1 + int if abs($_ - int ) >= 0.5; # Rounding
250 17 100       53 $_ *= -1 if $exch%2;
251              
252 17 100       210 return $fraction ? $_ / 10**($fraction * ($dm + 1)) : $_;
253             }
254             }
255              
256              
257             sub Solve_Det{
258 2   33 2 1 10 my $M = shift || croak("Missing matrix");
259 2   33     7 my $B = shift || croak("Missing vector");
260 2         4 my $opts = shift;
261              
262 2         3 my $rows = $#{ $M }; # number of rows
  2         5  
263 2         4 my $cols = $#{ $M->[0] }; # number of columns
  2         6  
264              
265 2         7 my $t = &test_matrix( $M );
266 2 50       12 if( $t > 3 ){
    50          
267 0         0 croak("Use of uninitialized value in matrix");
268              
269             }elsif( $t ){
270 0         0 croak("Matrix is not quadratic");
271             }
272              
273 2 50       4 croak("Vector doesn't correspond to a matrix") if $rows != $#{$B};
  2         8  
274 2 50       10 croak("Use of uninitialized value in vector") if scalar( grep ! defined $_, @$B );
275              
276 2 50       8 if( defined $opts ){
277 0 0       0 if( exists $opts->{'eqs'} ){
278 0 0       0 die "Unknown parameter \'$opts->{'eqs'}\'!\n"
279             unless $opts->{'eqs'}=~/^(?:row|column)/i;
280             }else{
281 0         0 $opts->{'eqs'} = 'row';
282             }
283             }else{
284 2         10 $opts->{'eqs'} = 'row';
285             }
286              
287 2         4 my $solve;
288              
289             # main determinant
290 2   50     6 my $det_main = &Det( $M, $opts ) || return undef; # no one solution
291              
292 2         7 for( my $v = 0; $v <= $cols; $v++ ){
293              
294 11         1267 my $R; # copy of matrix M
295 11         32 for( my $i = 0; $i <= $rows; $i++ ){
296              
297 65 50       957 if($opts->{'eqs'}=~/^col/i){
298 0 0       0 $R->[$i] = $v == $i ? $B : $M->[$i];
299              
300             }else{
301 65         210 for( my $j = 0; $j <= $cols; $j++ ){
302 407 100       1332 $R->[$i][$j] = $v == $j ? $B->[$i] : $M->[$i][$j];
303             }
304             }
305              
306             }
307              
308 11         31 my $det = &Det( $R, $opts );
309 11         16 my $dm = $det_main;
310              
311 11 50       28 if( $det ){
312 11 100       28 if( $dm < 0 ){
313 7         11 $dm *= -1;
314 7         283 $det *= -1;
315             }
316              
317             # Check Float (Fraction)
318 11         37 my $max_frac = &_max_len_frac([$dm, $det], 0);
319              
320 11 100       34 if($max_frac){
321 4         18 $_ *= 10**$max_frac for($dm, $det);
322             }
323              
324 11         49 my $gcd = bgcd(abs($det), abs($dm));
325 11 100       3000 if($gcd > 1){
326 10         1701 $det = $det / $gcd;
327 10         1462 $dm = $dm / $gcd;
328             }
329              
330 11 100       1956 $solve->[$v] = $dm == 1 ? $det : "$det/$dm";
331              
332             }else{
333 0         0 $solve->[$v] = 0;
334             }
335             }
336 2         302 $solve;
337             }
338              
339              
340             sub test_matrix{
341 6     6 1 13 my $M = shift;
342              
343 6 50       15 return 4 if scalar( grep{ grep ! defined $_, @{$_} } @$M );
  35         38  
  35         112  
344              
345 6 50       12 my $res = scalar( grep $#{$_} != $#{ $M }, @$M ) ? 1 : 0; # quadra
  35         46  
  35         382  
346 6 50       12 $res += 2 if scalar( grep $#{$_} != $#{ $M->[0] }, @$M ); # reqtan;
  35         50  
  35         1618  
347              
348 6         14 $res;
349             }
350              
351              
352             sub _max_len_frac{
353 115     115   236 my($M, $max_frac) = @_;
354              
355 115         191 for( @$M ){
356 664 100       55559 next if Math::BigInt::is_int($_);
357 69         13648 my(undef, $frac) = Math::BigFloat->new($_)->length();
358 69 100       20411 $max_frac = $frac if $frac > $max_frac;
359             }
360 115         9812 $max_frac;
361             }
362              
363             1;
364             __END__