Eigen学习笔记

最近由于课题原因,新接触到一个C++的线性代数模板库,名为Eigen(读作 ['aɪgən])。主页位于

http://eigen.tuxfamily.org/dox-devel/index.html

简单理解Eigen就是对矩阵和向量进行了抽象和建模,并且设计了相关的线性代运算的实现。简单尝试了之后发现上手很快,但是某些功能由于设计理念的独特性,导致最终的实现方案比较特殊。


简单看了一下Eigen的在线文档,记录几点用法。


系统是Ubuntu 16.04。


1 安装

非常特殊的是,作为C++库,Eigen不需要编译,而是以头文件的形式提供给用户的,之前用过的库貌似tinyXML也是这样。所以,所谓的安装在Linux上就是向/usr/local/include做了一个文件夹的符号链接。既创建符号链接在/usr/local/include/Eigen指向Eigen的解压后的位置。


2 初步测试

直接抄官方文档中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;
}

其中MatrixXd代表是运行时指定大小的矩阵(大写的X),使用双精度浮点数存储矩阵元素(最后的小写d)。可以看到,圆括号有重载。流输出也有重载。


由于创建了系统级别的符号链接,所以编译时不需要任何特殊设定,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;
}

运行结果如下。

Eigen学习笔记_第1张图片


于是矩阵加法,数乘,矩阵与向量的乘法都有运算符重载,很不错,有种MATLAB的感觉。


出了MatrixXd类和VectorXd类以外,Eigen还定义了常用维度的矩阵。根据Eigen文档的说法,在编译时能够确定矩阵或向量维度时,可以给Eigen优化的空间,并提供更高的执行效率。


3 Map, Stride, Reshape, Slicing和Block操作

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;
}

以上实例代码中,是在一个3x8的矩阵中,取出包括第1列在内的3列,每隔2列取1列。创建M2时的最后一个参数即为Stride类型的变量。在这个例子中,Map的Stride被定义为每隔一列将空过9个元素的存储位置。代码的执行效果如下图。

Eigen学习笔记_第2张图片

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;
}

在上面代码中,测试了col()函数等Eigen设计好用于获取某列某行,或者从开头或者结尾开始计算的连续多行或者多列的函数。执行结果如下图。

Eigen学习笔记_第3张图片


4 神奇的注意点

关于class内部声明Eigen对象的特殊问题。



你可能感兴趣的:(c++)