最近由于课题原因,新接触到一个C++的线性代数模板库,名为Eigen(读作 ['aɪgən])。主页位于
http://eigen.tuxfamily.org/dox-devel/index.html
简单理解Eigen就是对矩阵和向量进行了抽象和建模,并且设计了相关的线性代运算的实现。简单尝试了之后发现上手很快,但是某些功能由于设计理念的独特性,导致最终的实现方案比较特殊。
简单看了一下Eigen的在线文档,记录几点用法。
系统是Ubuntu 16.04。
非常特殊的是,作为C++库,Eigen不需要编译,而是以头文件的形式提供给用户的,之前用过的库貌似tinyXML也是这样。所以,所谓的安装在Linux上就是向/usr/local/include做了一个文件夹的符号链接。既创建符号链接在/usr/local/include/Eigen指向Eigen的解压后的位置。
直接抄官方文档中getting start的示例程序,测试了一下矩阵的创建,非常直接非常简单。
#include
#include
using Eigen::MatrixXd;
int main(void)
{
MatrixXd m(2, 2);
m(0, 0) = 3;
m(1, 0) = 2.5;
m(0, 1) = -1;
m(1, 1) = m(1, 0) + m(0, 1);
std::cout << m << std::endl;
return 0;
}
由于创建了系统级别的符号链接,所以编译时不需要任何特殊设定,g++即可查找到对应的头文件并且完成编译。程序执行的效果如下。
No big deal!
同样直接抄官方的示例代码
#include
#include
using namespace Eigen;
using namespace std;
int main(void)
{
MatrixXd m = MatrixXd::Random(3, 3);
m = (m + MatrixXd::Constant(3,3,1.2)) * 50;
cout << "m = " << endl << m << endl;
VectorXd v(3);
v << 1, 2, 3;
cout << "v = " << endl << v << endl;
cout << "m * v = " << endl << m * v << endl;
return 0;
}
于是矩阵加法,数乘,矩阵与向量的乘法都有运算符重载,很不错,有种MATLAB的感觉。
出了MatrixXd类和VectorXd类以外,Eigen还定义了常用维度的矩阵。根据Eigen文档的说法,在编译时能够确定矩阵或向量维度时,可以给Eigen优化的空间,并提供更高的执行效率。
Eigen默认采用列主导(column major)的数据存储形式,这点和FORTRAN还有MATLAB是一样的,当然也可以选择采用行主导,但是我感觉真心没必要。我一开始比较关心的是Eigen是否定义了比较方便的额slicing操作。但是似乎Eigen的Slicing和我理解的不太一样,有点复杂。Eigen的文档中,Slicing和Reshape是一起讲的,原因在于他们都是利用Eigen定义的Map功能实现的。简单理解Map就是将原始“连续内存存储”的数据,以矩阵形式重新组织。在使用Map时就需要原始数据,Map后的数据的维度形式,Map时使用的Stride设定。所谓Stride,既指矩阵中沿着矩阵列或行方向移动一个位置,内存中需要移动的位置数。这个需要移动的内存位置数与矩阵采用的存储方式有关(列或行主导)。Eigen定义了常用的Stride,既innerStride和outerStride。最好理解以上两个stride的方法就是看实例。innerStride既表示沿着矩阵的数据存储方向移动一个元素的位置,在内存中需要移动的
宽度。outerStride的含义就是不沿着数据存储方向移动一个位置,在内存中需要移动的宽度。还是用实例进行说明。
#include
#include
using namespace Eigen;
using namespace std;
int main(void)
{
MatrixXf M1 = MatrixXf::Random(3,8);
cout << "Column major input:" << endl << M1 << "\n";
cout << "M1.outerStride() = " << M1.outerStride() << endl;
cout << "M1.innerStride() = " << M1.innerStride() << endl;
Map > M2(
M1.data(), M1.rows(), (M1.cols()+2)/3, OuterStride<>(M1.outerStride()*3));
cout << "1 column over 3:" << endl << M2 << "\n";
return 0;
}
Reshape的操作与Slicing很像。
目测Slicing都必须从原始数据的最开始进行,想要原始矩阵中的某一列后者某一行,或者某一块,就需要Block操作。实际最经常用的就是取出一行或者一列的操作。设计了如下的实例代码。
#include
#include
using namespace Eigen;
using namespace std;
int main(void)
{
MatrixXf M1 = MatrixXf::Random(3,8);
cout << "Column major input:" << endl << M1 << "\n";
cout << "The first column is:" << endl << M1.col(0) << "\n";
cout << "The last column is: " << endl << M1.rightCols(1) << "\n";
cout << "The first row is: " << endl << M1.topRows<1>() << endl;
cout << "The last row is: " << endl << M1.bottomRows<1>() << endl;
return 0;
}
4 神奇的注意点
关于class内部声明Eigen对象的特殊问题。