Armadillo 的 solve(A, b) 从 Matlab、Eigen 返回不同的答案
Armadillo's solve(A, b) returning different answer from Matlab, Eigen
我正在使用 Armadillo 5.200 和 Visual Studio 2013 求解线性方程组 xA=b。我的代码通过求解 x=(A'\b')' 来计算这个,我正在使用 Armadillo 的 solve() 函数来求解线性方程组。
我的问题是返回给我的解决方案是不正确的——它不同于针对同一问题的 Eigen 和 Matlab 解决方案,它们是匹配的。我首先不只是使用 Eigen 的原因是它执行得太慢(我必须进行数百万次计算)而且我想看看使用 Armadillo 是否可以加快它的速度。我也很好奇为什么会出现这个错误。
这是一段说明问题的示例代码:
#include "stdafx.h"
#include <iostream>
#include <armadillo>
#include <Eigen/Dense>
// Matrix operation to find world coordinates.
arma::mat mrdivide(arma::mat A, arma::mat B){
arma::mat A_t = A.t();
arma::mat B_t = B.t();
return solve(A_t, B_t).t();
}
Eigen::MatrixXd mrdivide(Eigen::MatrixXd A, Eigen::MatrixXd B){
Eigen::MatrixXd A_t = A.transpose();
Eigen::MatrixXd B_t = B.transpose();
return ((A_t).colPivHouseholderQr().solve(B_t)).transpose();
}
int main(int argc, char* argv[])
{
Eigen::Matrix<double, 4, 3> M_eig;
M_eig << 761.544, 0, 0,
0, 761.544, 0,
639.5, 399.5, 1.0,
3.762513283904080e+06, 1.824431013104484e+06, 9.837714402800992e+03;
arma::mat M_arma;
M_arma << M_eig(0, 0) << M_eig(0, 1) << M_eig(0, 2) << arma::endr
<< M_eig(1, 0) << M_eig(1, 1) << M_eig(1, 2) << arma::endr
<< M_eig(2, 0) << M_eig(2, 1) << M_eig(2, 2) << arma::endr
<< M_eig(3, 0) << M_eig(3, 1) << M_eig(3, 2) << arma::endr;
Eigen::Matrix<double, 1, 3> pixelCoords_eig;
pixelCoords_eig << 457, 520, 1;
arma::mat pixelCoords_arma;
pixelCoords_arma << 457 << 520 << 1 << arma::endr;
Eigen::Matrix<double, 1, 4> worldCoords_eig;
arma::mat worldCoords_arma;
worldCoords_arma = mrdivide(M_arma, pixelCoords_arma);
worldCoords_eig = mrdivide(M_eig, pixelCoords_eig);
std::cout << "world coords arma: " << worldCoords_arma << std::endl;
std::cout << "world coords eig: " << worldCoords_eig << std::endl;
}
我的输出是:
world coords arma: 5.3599e-002 4.242e-001 1.3120e-001 8.8313e-005
world coors eig: .0978826 .439301 0 0.00010165
这里的本征解是正确的(Matlab 给我的,并且对我正在执行的计算具有逻辑意义)。为什么犰狳会给我错误的解决方案?
关于 Eigen 的速度,您可以通过这种方式移除所有堆分配来获得显着的提升(几乎快一个数量级):
Matrix<double, 1, 4> mrdivide(const Matrix<double, 4, 3> &A,
const Matrix<double, 1, 3> &B){
return A.transpose().colPivHouseholderQr().solve(B.transpose());
}
您还可以通过对 M_eig 使用行主矩阵,使 A.transpose() 是列主矩阵,从而多节省 10%:
Matrix<double, 4, 3,RowMajor> M_eig;
Matrix<double, 1, 4> mrdivide(const Matrix<double, 4, 3, RowMajor> &A,
const Matrix<double, 1, 3> &B);
最后,由于您的问题在数值上条件良好,您还可以使用基于 Cholesky 的求解器来获得额外的 x2 加速(在这种情况下,保留 M_eig 的默认存储):
Matrix<double, 1, 4> mrdivide(const Matrix<double, 4, 3> &A, const Matrix<double, 1, 3> &B){
return (A*A.transpose()).ldlt().solve(A*B.transpose());
}
为了完整起见,这里有一个实现 QR 和 LDLT 解决方案的独立示例:
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
Matrix<double, 1, 4> mrdivide_qr(const Matrix<double, 4, 3> &A,
const Matrix<double, 1, 3> &B){
return A.transpose().colPivHouseholderQr().solve(B.transpose());
}
Matrix<double, 1, 4> mrdivide_ldlt(const Matrix<double, 4, 3> &A, const Matrix<double, 1, 3> &B){
return (A*A.transpose()).ldlt().solve(A*B.transpose());
}
int main(int argc, char* argv[])
{
Matrix<double, 4, 3> M_eig;
M_eig << 761.544, 0, 0,
0, 761.544, 0,
639.5, 399.5, 1.0,
3.762513283904080e+06, 1.824431013104484e+06, 9.837714402800992e+03;
Matrix<double, 1, 3> pixelCoords_eig;
pixelCoords_eig << 457, 520, 1;
Matrix<double, 1, 4> worldCoords_eig;
worldCoords_eig = mrdivide_qr(M_eig, pixelCoords_eig);
std::cout << "world coords using QR: " << worldCoords_eig << std::endl;
worldCoords_eig = mrdivide_ldlt(M_eig, pixelCoords_eig);
std::cout << "world coords using LDLT: " << worldCoords_eig << std::endl;
}
mrdivide_ldlt
的速度是 mrdivide_qr
的两倍,后者本身比问题的 mrdivide
函数快得多。
解犰狳是正确的,因为你解决了超定系统。
因此,通过不同的方法得到的解决方案可能会有所不同。
下面的代码演示了如何使用 Armadilio 库,得到类似 Matlab 的结果。
为简单起见,我没有完全实现算法,也没有检查参数函数的正确性,因此交换行是人为的(并不是真正必要的)。
# include <iostream>
# include <armadillo>
# include <algorithm>
# include <array>
arma::mat qr_solve2(const arma::mat &A,const arma::mat &B);
arma::mat mrdivide(arma::mat A, arma::mat B)
{
return solve(A.t(), B.t());
}
int main()
{
std::array<double,12> arr = {
761.544 , 0 ,639.5,
3.762513283904080e+06,0 , 761.544,
399.5,1.824431013104484e+06,0,
0, 1.0, 9.837714402800992e+03
};
arma::mat A(4,3);
std::copy(arr.begin(),arr.end(),A.begin());
arma::mat B;
B << 457 << 520 << 1 << arma::endr;
arma::mat V = mrdivide(A,B);
std::cout<<V<<std::endl;
std::cout<<A.t()*V<<std::endl;
arma::mat Z = qr_solve2(A.t(),B.t());
std::cout<<Z<<std::endl;
std::cout<<A.t()*Z<<std::endl;
return 0;
}
arma::mat qr_solve2(const arma::mat &A,const arma::mat &B)
{
arma::mat Q, R;
arma::qr(Q,R,A);
unsigned int s = R.n_rows-1;
arma::mat R_ = R( arma::span(0,s), arma::span(0,s-1) ) ;
R_ = arma::join_horiz(R_,R.col(s+1));
arma::mat newB = Q.t()*B;
arma::mat X(s+1,1);
for (int i = s; i >= 0; i--)
{
X[i] = newB[i];
for (int j = s; j != i; j--)
X[i] = X[i] - R_(i,j) * X[j];
X[i] = X[i]/R_(i,i);
}
arma::mat res = X( arma::span(0,s-1), arma::span(0,0) ) ;
res = arma::join_vert(res,arma::mat(1,1, arma::fill::zeros));
res = arma::join_vert(res,X.row(s));
return res;
}
与往常一样,在编译时指示大小矩阵可以减少内存分配的成本,因此可以使用它来加速计算。
我正在使用 Armadillo 5.200 和 Visual Studio 2013 求解线性方程组 xA=b。我的代码通过求解 x=(A'\b')' 来计算这个,我正在使用 Armadillo 的 solve() 函数来求解线性方程组。
我的问题是返回给我的解决方案是不正确的——它不同于针对同一问题的 Eigen 和 Matlab 解决方案,它们是匹配的。我首先不只是使用 Eigen 的原因是它执行得太慢(我必须进行数百万次计算)而且我想看看使用 Armadillo 是否可以加快它的速度。我也很好奇为什么会出现这个错误。
这是一段说明问题的示例代码:
#include "stdafx.h"
#include <iostream>
#include <armadillo>
#include <Eigen/Dense>
// Matrix operation to find world coordinates.
arma::mat mrdivide(arma::mat A, arma::mat B){
arma::mat A_t = A.t();
arma::mat B_t = B.t();
return solve(A_t, B_t).t();
}
Eigen::MatrixXd mrdivide(Eigen::MatrixXd A, Eigen::MatrixXd B){
Eigen::MatrixXd A_t = A.transpose();
Eigen::MatrixXd B_t = B.transpose();
return ((A_t).colPivHouseholderQr().solve(B_t)).transpose();
}
int main(int argc, char* argv[])
{
Eigen::Matrix<double, 4, 3> M_eig;
M_eig << 761.544, 0, 0,
0, 761.544, 0,
639.5, 399.5, 1.0,
3.762513283904080e+06, 1.824431013104484e+06, 9.837714402800992e+03;
arma::mat M_arma;
M_arma << M_eig(0, 0) << M_eig(0, 1) << M_eig(0, 2) << arma::endr
<< M_eig(1, 0) << M_eig(1, 1) << M_eig(1, 2) << arma::endr
<< M_eig(2, 0) << M_eig(2, 1) << M_eig(2, 2) << arma::endr
<< M_eig(3, 0) << M_eig(3, 1) << M_eig(3, 2) << arma::endr;
Eigen::Matrix<double, 1, 3> pixelCoords_eig;
pixelCoords_eig << 457, 520, 1;
arma::mat pixelCoords_arma;
pixelCoords_arma << 457 << 520 << 1 << arma::endr;
Eigen::Matrix<double, 1, 4> worldCoords_eig;
arma::mat worldCoords_arma;
worldCoords_arma = mrdivide(M_arma, pixelCoords_arma);
worldCoords_eig = mrdivide(M_eig, pixelCoords_eig);
std::cout << "world coords arma: " << worldCoords_arma << std::endl;
std::cout << "world coords eig: " << worldCoords_eig << std::endl;
}
我的输出是:
world coords arma: 5.3599e-002 4.242e-001 1.3120e-001 8.8313e-005
world coors eig: .0978826 .439301 0 0.00010165
这里的本征解是正确的(Matlab 给我的,并且对我正在执行的计算具有逻辑意义)。为什么犰狳会给我错误的解决方案?
关于 Eigen 的速度,您可以通过这种方式移除所有堆分配来获得显着的提升(几乎快一个数量级):
Matrix<double, 1, 4> mrdivide(const Matrix<double, 4, 3> &A,
const Matrix<double, 1, 3> &B){
return A.transpose().colPivHouseholderQr().solve(B.transpose());
}
您还可以通过对 M_eig 使用行主矩阵,使 A.transpose() 是列主矩阵,从而多节省 10%:
Matrix<double, 4, 3,RowMajor> M_eig;
Matrix<double, 1, 4> mrdivide(const Matrix<double, 4, 3, RowMajor> &A,
const Matrix<double, 1, 3> &B);
最后,由于您的问题在数值上条件良好,您还可以使用基于 Cholesky 的求解器来获得额外的 x2 加速(在这种情况下,保留 M_eig 的默认存储):
Matrix<double, 1, 4> mrdivide(const Matrix<double, 4, 3> &A, const Matrix<double, 1, 3> &B){
return (A*A.transpose()).ldlt().solve(A*B.transpose());
}
为了完整起见,这里有一个实现 QR 和 LDLT 解决方案的独立示例:
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
Matrix<double, 1, 4> mrdivide_qr(const Matrix<double, 4, 3> &A,
const Matrix<double, 1, 3> &B){
return A.transpose().colPivHouseholderQr().solve(B.transpose());
}
Matrix<double, 1, 4> mrdivide_ldlt(const Matrix<double, 4, 3> &A, const Matrix<double, 1, 3> &B){
return (A*A.transpose()).ldlt().solve(A*B.transpose());
}
int main(int argc, char* argv[])
{
Matrix<double, 4, 3> M_eig;
M_eig << 761.544, 0, 0,
0, 761.544, 0,
639.5, 399.5, 1.0,
3.762513283904080e+06, 1.824431013104484e+06, 9.837714402800992e+03;
Matrix<double, 1, 3> pixelCoords_eig;
pixelCoords_eig << 457, 520, 1;
Matrix<double, 1, 4> worldCoords_eig;
worldCoords_eig = mrdivide_qr(M_eig, pixelCoords_eig);
std::cout << "world coords using QR: " << worldCoords_eig << std::endl;
worldCoords_eig = mrdivide_ldlt(M_eig, pixelCoords_eig);
std::cout << "world coords using LDLT: " << worldCoords_eig << std::endl;
}
mrdivide_ldlt
的速度是 mrdivide_qr
的两倍,后者本身比问题的 mrdivide
函数快得多。
解犰狳是正确的,因为你解决了超定系统。
因此,通过不同的方法得到的解决方案可能会有所不同。
下面的代码演示了如何使用 Armadilio 库,得到类似 Matlab 的结果。
为简单起见,我没有完全实现算法,也没有检查参数函数的正确性,因此交换行是人为的(并不是真正必要的)。
# include <iostream>
# include <armadillo>
# include <algorithm>
# include <array>
arma::mat qr_solve2(const arma::mat &A,const arma::mat &B);
arma::mat mrdivide(arma::mat A, arma::mat B)
{
return solve(A.t(), B.t());
}
int main()
{
std::array<double,12> arr = {
761.544 , 0 ,639.5,
3.762513283904080e+06,0 , 761.544,
399.5,1.824431013104484e+06,0,
0, 1.0, 9.837714402800992e+03
};
arma::mat A(4,3);
std::copy(arr.begin(),arr.end(),A.begin());
arma::mat B;
B << 457 << 520 << 1 << arma::endr;
arma::mat V = mrdivide(A,B);
std::cout<<V<<std::endl;
std::cout<<A.t()*V<<std::endl;
arma::mat Z = qr_solve2(A.t(),B.t());
std::cout<<Z<<std::endl;
std::cout<<A.t()*Z<<std::endl;
return 0;
}
arma::mat qr_solve2(const arma::mat &A,const arma::mat &B)
{
arma::mat Q, R;
arma::qr(Q,R,A);
unsigned int s = R.n_rows-1;
arma::mat R_ = R( arma::span(0,s), arma::span(0,s-1) ) ;
R_ = arma::join_horiz(R_,R.col(s+1));
arma::mat newB = Q.t()*B;
arma::mat X(s+1,1);
for (int i = s; i >= 0; i--)
{
X[i] = newB[i];
for (int j = s; j != i; j--)
X[i] = X[i] - R_(i,j) * X[j];
X[i] = X[i]/R_(i,i);
}
arma::mat res = X( arma::span(0,s-1), arma::span(0,0) ) ;
res = arma::join_vert(res,arma::mat(1,1, arma::fill::zeros));
res = arma::join_vert(res,X.row(s));
return res;
}
与往常一样,在编译时指示大小矩阵可以减少内存分配的成本,因此可以使用它来加速计算。