尝试自己在Perl中实现矩阵求逆,我发现An Efficient and Simple Algorithm for Matrix Inversion(本文只有两页)。
尝试在Perl中实现它后,我发现它不起作用。 我花了很多时间 来找出问题所在,所以我得出结论
在展示代码之前,这是一个调试会话,其中包含来自Wikipedia: Inverse Matrix的示例:
DB<229> $m=[[2,5],[1,3]]
DB<230> x invert($m)
pe[0] == 2
(pivot row 0) 2x2:
2.000 2.500
1.000 3.000
(pivot column 0) 2x2:
2.000 2.500
-0.500 3.000
(rest 0) 2x2:
2.000 2.500
-0.500 1.750
(pivot 0) 2x2:
0.500 2.500
-0.500 1.750
pe[1] == 1.75
(pivot row 1) 2x2:
0.500 2.500
-0.286 1.750
(pivot column 1) 2x2:
0.500 -1.429
-0.286 1.750
(rest 1) 2x2:
0.908 -1.429
-0.286 1.750
(pivot 1) 2x2:
0.908 -1.429
-0.286 0.571
0 1
1 3.5
DB<231>
这是我编写的代码:
#!/usr/bin/perl -w
use 5.026;
use strict;
# invert matrix
# An Efficient and Simple Algorithm for Matrix Inversion
# Ahmad Farooq, King Khalid University, Saudi Arabia
# Khan Hamid, National University of Computer and Emerging Sciences (NUCES),
# Pakistan
sub invert($)
{
my $m = shift; # matrix is an array of rows
my ($pp, $det);
my ($rp, $pe);
my $n = scalar(@$m);
for ($pp = 0, $det = 1.0; $pp < $n; ++$pp) {
$rp = $m->[$pp]; # pivot row
$pe = $rp->[$pp]; # pivot element
print "pe[$pp] == $pe\n";
last if ($pe == 0); # Epsilon test?
$det *= $pe;
# calculate pivot row
for (my $j = 0; $j < $n; ++$j) {
next if ($j == $pp);
$rp->[$j] /= $pe;
}
pm($m, "pivot row $pp");
# calculate pivot column
for (my $i = 0; $i < $n; ++$i) {
next if ($i == $pp);
$m->[$i]->[$pp] /= -$pe;
}
pm($m, "pivot column $pp");
for (my $j = 0; $j < $n; ++$j) {
next if ($j == $pp);
for (my ($i, $rj) = (0, $m->[$j]); $i < $n; ++$i) {
next if ($i == $pp);
$rj->[$i] += $rp->[$j] * $m->[$i]->[$pp];
}
}
pm($m, "rest $pp");
$rp->[$pp] = 1.0 / $pe;
pm($m, "pivot $pp");
}
return ($pe != 0.0, $det);
}
pm()
函数只是用于调试目的的“打印矩阵”:
# print matrix
sub pm($;$)
{
my ($m, $label) = @_;
my $n = scalar(@$m);
print "($label) " if ($label);
print "${n}x${n}:\n";
for (my $i = 0; $i < $n; ++$i) {
for (my $j = 0; $j < $n; ++$j) {
if (defined(my $v = $m->[$i]->[$j])) {
printf('%8.3f', $v);
} else {
print ' ???????';
}
}
print "\n";
}
}
有什么见解吗?
复制提示(于2019-08-28添加)
我以为这很明显,但以防万一: 如果要重现调试会话中显示的输出,也许只需在代码末尾添加以下两行即可:
my $m=[[2,5],[1,3]]; # matrix to invert
print join(', ', invert($m)), "\n"; # invert $m, printing result
注意(于2019-09-02添加):
对于Wikipedia文章($m = [[1, 2, 0], [2, 4, 1], [2, 1, 0]]
)中给出的3x3矩阵,该算法失败,因此实际的实现应朝着改进的算法发展(可以选择对角线之外的枢轴元素)。
答案 0 :(得分:3)
如有疑问,请编写测试。
首先,将您的代码放入模块(lib/My/Matrix.pm
或任何您想调用的模块)中:
package My::Matrix; # this must match the file name
use strict;
use warnings;
use Exporter qw(import);
our @EXPORT_OK = qw( invert pm );
# your code here ...
1; # at end of module
有很多关于编写模块的文档,不确定perldoc perlmod是否是一个很好的起点。
现在编写测试-文档为here(t / 001-invert.t):
#!perl
use strict;
use warnings;
use Test::More;
use Matrix qw(invert);
ok_invert( [[1,0], [0,1]], [[1,0], [0,1]], "unit matrix" );
# insert more matrices here
done_testing;
sub ok_invert {
my ($input, $output, $msg) = @_;
invert( $output );
is_deeply $input, $output, $msg
or diag "got: ", explain $input, "expected: ", explain $output;
};
如果要运行多个测试,请以perl -Ilib t/001-invert.t
或prove -Ilib t
的身份运行测试。
现在,您可以将简单的极端情况添加到测试中,直到发现问题为止。
当然,手动找到正确的逆矩阵很繁琐,因此您可能要使用乘法。因此,改善代码的下一步是:
侧注。确保功能returns the desired value and does not modify its arguments通常是一个好主意。并非总是可能的,但是在可能的情况下,它可以节省大量的调试时间。
is_unit_matrix
检查; sub ok_invert {
my ($input, $msg) = @_;
my ($invert, $det) = invert( $input );
ok is_unit_matrix( multiply( $invert, $input ) ), $msg
or diag explain $invert, " is not the inverse of ", explain $input;
}
希望这会有所帮助。
答案 1 :(得分:2)
根据所引用的论文,应该使用旧的枢轴行值来计算第7步,因此以下内容似乎对我有用:
sub invert($)
{
my $m = shift; # matrix is an array of rows
my ($pp, $det);
my ($rp, $pe);
my $n = scalar(@$m);
for ($pp = 0, $det = 1.0; $pp < $n; ++$pp) {
$rp = $m->[$pp]; # pivot row
$pe = $rp->[$pp]; # pivot element
last if ($pe == 0); # Epsilon test?
$det *= $pe;
# calculate pivot column
for (my $i = 0; $i < $n; ++$i) {
next if ($i == $pp);
$m->[$i][$pp] /= -$pe;
}
for (my $j = 0; $j < $n; ++$j) { # row index
next if ($j == $pp);
for (my ($i, $rj) = (0, $m->[$j]); $i < $n; ++$i) {
next if ($i == $pp);
$rj->[$i] += $rp->[$i] * $m->[$j]->[$pp];
}
}
# calculate pivot row
for (my $j = 0; $j < $n; ++$j) {
next if ($j == $pp);
$rp->[$j] /= $pe;
}
$rp->[$pp] = 1.0 / $pe;
}
return ($pe != 0.0, $det);
}
需要与维基百科中的结果相匹配的修正:
--- newinvert.pl~ 2019-08-29 21:22:16.135160055 +0200
+++ newinvert.pl 2019-08-29 21:32:10.995144732 +0200
@@ -20,7 +20,7 @@
next if ($j == $pp);
for (my ($i, $rj) = (0, $m->[$j]); $i < $n; ++$i) {
next if ($i == $pp);
- $rj->[$i] += $rp->[$i] * $m->[$j]->[$pp];
+ $rj->[$i] += $rp->[$j] * $m->[$i]->[$pp];
}
}
# calculate pivot row
示例会话(包括我的pm()
):
> perl -d printmatrix.pl
Loading DB routines from perl5db.pl version 1.51
Editor support available.
Enter h or 'h h' for help, or 'man perldebug' for more help.
main::(printmatrix.pl:20): 1;
DB<1> require "./newinvert.pl" # this is ungly, forgive!
./newinvert.pl did not return a true value at (eval 6)[/usr/lib/perl5/5.26.1/perl5db.pl:738] line 2.
DB<2> $m=[[2,5],[1,3]]
DB<4> pm($m)
2x2:
2.000 5.000
1.000 3.000
DB<5> x invert($m)
0 1
1 1
DB<6> pm($m)
2x2:
3.000 -5.000
-1.000 2.000
回归测试的结果
# https://github.com/wollmers/matrix-inverse-Farooq/blob/master/matrix_inversion_new.pl
$ perl matrix_inversion_new.pl
[...]
(invert_hakon 01_wiki input $A) 2x2:
2.000 5.000
1.000 3.000
(invert_hakon 01_wiki result $C) 2x2:
3.000 -5.000
-1.000 2.000
ok 10 - 01_wiki invert_hakon Ainv
ok 11 - 01_wiki invert_hakon det: 1
(invert_hakon 02_wiki input $A) 2x2:
2.000 3.000
1.000 2.000
(invert_hakon 02_wiki result $C) 2x2:
2.000 -3.000
-1.000 2.000
ok 12 - 02_wiki invert_hakon Ainv
ok 13 - 02_wiki invert_hakon det: 1
(invert_hakon 03_author_1 input $A) 3x3:
1.000 1.000 3.000
1.000 3.000 -3.000
-2.000 -4.000 -4.000
(invert_hakon 03_author_1 result $C) 3x3:
3.000 1.000 1.500
-1.250 -0.250 -0.750
-0.250 -0.250 -0.250
ok 14 - 03_author_1 invert_hakon Ainv
ok 15 - 03_author_1 invert_hakon det: -8
[...]
答案 2 :(得分:2)
原始论文中的伪代码不正确。
到目前为止我已经完成的步骤:
Math::Matrix
作为参考进行了测试至少我读过纸上的笔记:
请注意,在以下算法的步骤7中,a'[i,p] LHS上的表示枢轴行的最新值 将在计算中使用。
此注释并不十分准确。经过多次尝试,我放弃了,想在这里发表我的发现,并阅读HåkonHægland的答案。是的,他的解决方案有效,并且赢得了荣誉。
如果伪代码中的步骤重新排序,则它会通过我的3个测试:
Step 1
Step 2
Step 3
Step 4
Step 6
Step 7
Step 5
Step 8
Step 9
Step 10
以下是包含伪代码并使用原始命名的版本:
sub invert_corr($) {
my $A = shift; # matrix is an array of rows
my $n = scalar(@$A);
# Step 1: Let p = 0, d = 1;
my $p = 0;
my $det;
# Step 2: p <= p +1
for (my $pi = 0,$det = 1.0; $pi < $n; ++$pi) {
$p = $pi;
# Step 3: If a[p,p] == 0 then cannot calculate inverse, go to step 10.
if ($A->[$p]->[$p] == 0) { last; }
# Step 4: d <= d x a[p, p]
$det = $det * $A->[$p]->[$p];
# Step 6: Calculate the new elements of the pivot column by:
# a_new[i,p] <= -(a[i,p] / a[p,p]) where i = 1 .. n, i != p
STEP6: for (my $i = 0; $i < $n; ++$i) {
if ($i == $p) { next STEP6; }
$A->[$i]->[$p] = -($A->[$i]->[$p] / $A->[$p]->[$p]);
}
# Step 7: Calculate the rest of the new elements by:
# a_new[i,j] <= a[i,j] + a[p,j] x a_new[i,p]
# where i = 1 .. n, j = 1 .. n, & i,j != p
OUTER7: for (my $i = 0; $i < $n; ++$i) {
if ($i == $p) { next OUTER7; }
INNER7: for (my $j = 0; $j < $n; ++$j) {
if ($j == $p) { next INNER7; }
# Note that in step 7 of the following algorithm a'[i, p]
# on the LHS means that the latest value of the pivot row
# is to be used in the calculations.
$A->[$i]->[$j] = $A->[$i]->[$j] + $A->[$p]->[$j] * $A->[$i]->[$p];
}
}
# Step 5: Calculate the new elements of the pivot row by:
# a_new[p,j] <= a[p,j] / a[p,p] where j = 1 .. n, j != p
STEP5: for (my $j = 0; $j < $n; ++$j) {
# next if ($j == $p);
if ($j == $p) { next STEP5; }
$A->[$p]->[$j] = $A->[$p]->[$j] / $A->[$p]->[$p];
}
# Step 8: Calculate the new value of the current pivot location:
# a_new[p,p] <= 1 / a_new[p,p]
$A->[$p]->[$p] = 1.0 / $A->[$p]->[$p];
# Step 9: If p < n go to step 2 (n the dimension of the matrix A).
}
# Step 10: Stop. If inverse exists, A contains the inverse and d is the determinant.
if ($A->[$p]->[$p] != 0.0) {
return ($A->[$p]->[$p] != 0.0, $det, $A);
}
return ($A->[$p]->[$p] != 0.0);
}
github上提供了包含测试的完整代码,可能对调试很有用。
答案 3 :(得分:1)