1. 初始Eigen
Eigen简介...
本节主要介绍如何开始使用Eigen,并简单的介绍Eigen的一些概念,希望给读者一个较为直观的认识。 后续章节会结合线性代数详细的介绍Eigen的设计思想和使用方法。
1.1. Eigen的安装和编译
Eigen项目的编译过程只用到了头文件,并不需要库文件。 因此只要把从官网 或者这里下载的源码包解压到目标目录下就可以了。 下面我们以一个例程来体验一下Eigen的编译和使用过程。
#include <iostream>
#include <Eigen/Dense>
using Eigen::MatrixXd;
int main()
{
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;
}
在编译该程序时不需要额外链接什么库文件,编译指令如下,其中环境变量${PATH_TO_EIGEN}指示了Eigen的安装路径。
g++ -I${PATH_TO_EIGEN} example.cpp -o example
编译成功后运行程序就可以得到如下的输出:
3 -1
2.5 1.5
在这个程序中,创建了一个2×2的矩阵。 Eigen定义了各种类型的矩阵,这里用的是MatrixXd,可以定义任意大小的矩阵(其中X指示矩阵大小任意, d则说明矩阵的每个元素为double类型)。 然后分别矩阵的每个元素赋值,最后通过标准输出流打印矩阵。
1.2. 矩阵的定义与实现
考虑如下的例程:
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
using namespace std;
int main()
{
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 << "m * v =" << endl << m * v << endl;
}
在这个例程中我们随机生成了一个3×3的MatrixXd类型矩阵, 需要注意的是Random函数的值域为[-1,1],然后通过一系列的运算映射到区间[10,100]中。 接着我们定义了一个3维的VectorXd向量,并通过"<<"运算符进行初始化。 与MatrixXd类似,VectorXd可以定义任意长度的向量。最后打印矩阵与向量的乘积。
在这个例程中,我们通过MatrixXd和VectorXd定义了任意长度的矩阵和向量,具体的大小只能在运行时确定。 Eigen还提供了一种定义确定大小的矩阵和向量的方法,其大小在编译过程中就可以确定,参考如下的例程:
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
using namespace std;
int main()
{
Matrix3d m = Matrix3d::Random();
m = (m + Matrix3d::Constant(1.2)) * 50;
cout << "m =" << endl << m << endl;
Vector3d v(1,2,3);
cout << "m * v =" << endl << m * v << endl;
}
这个例程的逻辑和输出跟上一个例程是一样的,所不同的是这个例程中定义的矩阵和向量的大小在编译过程中就已经确定了。 使用固定大小(fixed-size)的矩阵和向量的好处是可以快一点,而且可以在编译过程中进行类型检测,避免一些数学上不成立的计算式。 例如我们不能拿一个4×4的矩阵去乘一个3维的向量,编译器通过Matrix4d和Vector3d的类型检测就可以报错, 而使用可变大小的MatrixXd和VectorXd,则不能在编译时刻就检测到错误。 fixed-size矩阵的缺点就是编译时间长而且生成的可执行文件比较大,一般建议对4维以下的矩阵和向量使用。
1.3. 模板类Matrix
在Eigen中,所有的矩阵(matrix)和向量(vector)都是模板类Matrix的对象。其中向量是只有一行或者一列的特殊矩阵。 Matrix类一共有6个参数,通常我们只关心前三个参数,剩余的三个参数有默认值。我们先重点考虑前三个参数:
Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>
- Scalar指定了矩阵元素的数据类型,例如Scalar为double时,所生成矩阵元素就是double类型。
- RowsAtCompileTime指定了编译时矩阵行数
- ColsAtCompileTime指定了编译时矩阵列数
Eigen定义了一些常用的矩阵类型,例如我们已经见过的Matrix3d和Vector3d的声明如下:
typedef Matrix<double, 3, 3> Matrix3d;
typedef Matrix<double, 3, 1> Vector3d;
此外,还可以定义行向量:
typedef Matrix<double, 1, 3> RowVector3d;
参数RowsAtCompileTime和ColsAtCompileTime也可以接受一种特殊的值——Dynamic, 它将告知编译器矩阵的内存在运行时动态的分配,其大小在编译时是不知道的。前面例子中提到的MatrixXd和VectorXd声明如下:
typedef Matrix<double, Dynamic, Dynamic> MatrixXd;
typedef Matrix<double, Dynamic, 1> VectorXd;
我们也可以根据自己的需要定义特殊的矩阵类型,例如下面的矩阵确定只有三行,其列长度是不确定的。
Matrix<double, 3, Dynamic>
Eigen定义了如下的一些常用矩阵类型:
- MatrixNt - Matrix<type, N, N>. 例如MatrixXi表示Matrix<int, Dynamic, Dynamic>
- VectorNt - Matrix<type, N, 1>. 例如Vector3f表示Matrix<float, 3, 1>
- RowVectorNt - Matrix<type, 1, N>. 例如RowVector4d表示Matrix<double, 1, 4>
其中:
- N可以是2,3,4或者X(表示Dynamic)中的任意一个
- t可以是i(int), f(float), d(double), cf(complex)或者是cd(complex).
1.4. 构造函数与初始化
每种类型的矩阵都有一个默认的构造函数,在该构造函数中既不会动态的分配内存也不会对矩阵中的元素初始化。 我们可以这样定义两个矩阵变量:
Matrix3d a;
MatrixXd b;
其中,a为一个3×3的矩阵有一段double[9]的未初始化的线性内存(with a plain double[9] array of uninitialized coefficients)。 b则是一个0×0的动态矩阵,其内存空间尚未分配。
我们也可以在构造时指定矩阵的大小,对于矩阵需要指定行数和列数其中行数在前,对于向量只需要指定长度即可。 在该构造函数中只是分配了指定大小的内存,并不会对矩阵中元素进行初始化。例如:
MatrixXf a(10, 15);
VectorXf b(30);
其中,a为一个10×15的动态矩阵,b为一个长度为30的列向量。它们均已经分配了内存空间,但都没有进行初始化。
需要说明的是,为了统一fixed-size和dynamic-size的矩阵的接口, 对于fixed-size的矩阵申明矩阵大小也是合法的,只是这样做没有什么卵用。例如:
Matrix3f a(3,3);
对于长度小于4的fixed-size的向量可以在构造函数中赋值,例如:
Vector2d a(5.0, 6.0);
Vector3d b(5.0, 6.0, 7.0);
Vector4d c(5.0, 6.0, 7.0, 8.0);
此外,Eigen还提供了一种comma-initializer的语法,我们可以通过如下的方式初始化一个矩阵:
Matrix3f m;
m << 1, 2, 3,
4, 5, 6,
7, 8, 9;
关于初始化我们先了解这么多,以后会有详细的一章专门介绍。
1.5. 访问矩阵元素
在Eigen中通过对运算符'()'的重载实现了矩阵元素的访问器。 对于矩阵行索引在前,对于向量只需要给出元素索引即可。索引均是从0开始计数的。如下是一个访问矩阵和向量元素的例子:
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
int main()
{
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 << "Here is the matrix m: \n" << m << std::endl;
std::cout << "and the entries of m are:" << std::endl;
std::cout << "m(0) = " << m(0) << std::endl;
std::cout << "m(1) = " << m(1) << std::endl;
std::cout << "m(2) = " << m(2) << std::endl;
std::cout << "m(3) = " << m(3) << std::endl;
VectorXd v(2);
v(0) = 4;
v(1) = v(0) - 1;
std::cout << "Here is the vector v: \n" << v << std::endl;
std::cout << "and the entries of v are:" << std::endl;
std::cout << "v(0) = " << v(0) << "\tv[0] = " << v[0] << std::endl;
std::cout << "v(1) = " << v(1) << "\tv[1] = " << v[1] << std::endl;
}
在这个例子中,我们分别定义了一个矩阵和一个向量,然后通过元素直接赋值,最后输出。其输出结果如下:
Here is the matrix m:
3 -1
2.5 1.5
and the entries of m are:
m(0) = 3
m(1) = 2.5
m(2) = -1
m(3) = 1.5
Here is the vector v:
4
3
and the entries of v are:
v(0) = 4 v[0] = 4
v(1) = 3 v[1] = 3
值得注意的是,对于矩阵我们也可以通过m(index)来访问每个元素。 这里依次访问每一列的元素,这种访问方式称为列访问(column-major)。 实际上我们还可以进行行访问(row-major),这取决于矩阵是按行存储的还是按列存储的,默认是列存储(column-major)的方式。 关于矩阵的存储方式我们会在后续章节中予以详细的介绍。
此外,Eigen还提供了'[]'的元素访问方式,但是这种访问方式只限于对向量使用。 因为有些c++编译器会把matrix[i,j]和matrix[j]编译成一样的机器码。