EigenC++线性代数库入门教程
简单来说,Eigen是一个开源的C++模板库,专为线性代数、矩阵运算、向量计算等设计。它的名字来源于德语,意为"特征值"(线性代数中的重要概念)。为什么这么多人喜欢用Eigen?纯头文件实现:只需包含头文件,无需额外链接库文件,超级方便!高性能:底层优化得非常彻底,自动使用SIMD指令集加速(SSE、AVX等)简洁的API:接口设计很直观,几乎和数学公式一样易读灵活性强:从小型嵌入式系统到大型高性
文章目录
在数值计算的世界里,有一个名字几乎无人不知——Eigen!这个C++线性代数库简直是数学计算的瑞士军刀,不管你是搞机器学习、计算机图形学,还是科学计算,它都能轻松应对。今天就带大家一起揭开这个强大工具的神秘面纱!
什么是Eigen?
简单来说,Eigen是一个开源的C++模板库,专为线性代数、矩阵运算、向量计算等设计。它的名字来源于德语,意为"特征值"(线性代数中的重要概念)。
为什么这么多人喜欢用Eigen?看这些优点就知道了:
- 纯头文件实现:只需包含头文件,无需额外链接库文件,超级方便!
- 高性能:底层优化得非常彻底,自动使用SIMD指令集加速(SSE、AVX等)
- 简洁的API:接口设计很直观,几乎和数学公式一样易读
- 灵活性强:从小型嵌入式系统到大型高性能计算,都能胜任
- 免费开源:使用MPL2许可证,商业项目也能放心用
环境准备
开始使用Eigen前,我们需要先把环境准备好。
安装Eigen
因为Eigen是纯头文件库,所以"安装"其实就是下载并解压而已!
方法一:直接下载
- 访问Eigen官方网站
- 下载最新稳定版(撰写本文时是3.4.0)
- 解压到你喜欢的位置
方法二:包管理器安装
如果你用Ubuntu/Debian:
sudo apt-get install libeigen3-dev
macOS用户可以用Homebrew:
brew install eigen
Windows用户推荐用vcpkg:
vcpkg install eigen3
在项目中使用Eigen
因为Eigen是纯头文件库,你只需要在编译时指定include路径就行了。
CMake项目中使用(强烈推荐这种方式!):
# 在CMakeLists.txt中
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIRS})
# 然后创建你的目标
add_executable(my_program main.cpp)
直接编译时:
g++ -I/path/to/eigen main.cpp -o my_program
Eigen基础入门
好了,环境准备完毕,让我们开始写代码吧!先从最基本的矩阵和向量操作开始。
第一个Eigen程序
#include <iostream>
#include <Eigen/Dense> // 包含稠密矩阵相关的所有模块
int main() {
// 创建一个2x2的矩阵
Eigen::Matrix2d mat; // 2x2双精度浮点数矩阵
mat << 1, 2,
3, 4;
std::cout << "矩阵mat:\n" << mat << std::endl;
// 计算行列式
double det = mat.determinant();
std::cout << "行列式:" << det << std::endl;
return 0;
}
运行这个程序,你会看到:
矩阵mat:
1 2
3 4
行列式:-2
恭喜!你已经成功创建了第一个使用Eigen的程序!
常用的矩阵和向量类型
Eigen提供了多种预定义的矩阵和向量类型,命名规则也很简单:
Matrix<数据类型, 行数, 列数, 选项, 最大行数, 最大列数>
但实际上,我们通常使用预定义的类型别名:
// 常用矩阵类型
Eigen::Matrix2d // 2x2双精度矩阵
Eigen::Matrix3f // 3x3单精度矩阵
Eigen::Matrix4i // 4x4整数矩阵
// 动态大小矩阵
Eigen::MatrixXd // 动态大小双精度矩阵
Eigen::MatrixXf // 动态大小单精度矩阵
// 向量(列向量)
Eigen::Vector2d // 2维双精度向量
Eigen::Vector3f // 3维单精度向量
Eigen::VectorXd // 动态大小双精度向量
// 行向量
Eigen::RowVector3d // 3维双精度行向量
Eigen::RowVectorXf // 动态大小单精度行向量
命名规则很好记:
Matrix开头表示矩阵- 数字表示固定大小(如2、3、4)
X表示动态大小- 后缀表示数据类型:
d=double,f=float,i=int
矩阵的创建与初始化
Eigen提供了多种创建和初始化矩阵的方法:
1. 使用逗号初始化器
Eigen::Matrix3d mat;
mat << 1, 2, 3,
4, 5, 6,
7, 8, 9;
2. 使用构造函数(仅适用于动态大小)
Eigen::MatrixXd mat(3, 3); // 创建3x3矩阵
3. 特殊矩阵
// 零矩阵
Eigen::Matrix3d zero = Eigen::Matrix3d::Zero();
// 单位矩阵
Eigen::Matrix3d identity = Eigen::Matrix3d::Identity();
// 全1矩阵
Eigen::Matrix2d ones = Eigen::Matrix2d::Ones();
// 随机矩阵
Eigen::MatrixXd random = Eigen::MatrixXd::Random(3, 3);
// 常量矩阵
Eigen::Matrix2d constant = Eigen::Matrix2d::Constant(5.0); // 所有元素都是5.0
矩阵基本运算
矩阵的基本运算非常直观,就像普通的算术运算一样:
Eigen::Matrix2d a, b, c;
a << 1, 2,
3, 4;
b << 5, 6,
7, 8;
// 加法
c = a + b;
// 减法
c = a - b;
// 标量乘法
c = 2.0 * a;
// 矩阵乘法
c = a * b;
// 元素乘法(Hadamard乘积)
c = a.array() * b.array();
// 矩阵转置
c = a.transpose();
// 矩阵逆
c = a.inverse();
注意这里的.array(),它是Eigen的一个特殊设计:Eigen区分了矩阵运算和元素级运算。
- 矩阵表达式:执行线性代数运算(如矩阵乘法)
- 数组表达式:执行元素级运算(如元素乘法)
你可以通过.array()和.matrix()在两者之间转换。
实战案例:线性方程组求解
线性方程组是线性代数中最基本的问题之一。比如求解下面的方程组:
2x + y = 1
x + 3y = 2
用Eigen可以这样解决:
#include <iostream>
#include <Eigen/Dense>
int main() {
Eigen::Matrix2d A;
Eigen::Vector2d b;
// 设置系数矩阵
A << 2, 1,
1, 3;
// 设置常数向量
b << 1, 2;
// 求解线性方程组 Ax = b
Eigen::Vector2d x = A.colPivHouseholderQr().solve(b);
std::cout << "方程组的解为:\n" << x << std::endl;
// 验证解是否正确
std::cout << "验证:A*x =\n" << A*x << std::endl;
return 0;
}
输出结果:
方程组的解为:
0.2
0.6
验证:A*x =
1
2
Eigen提供了多种解线性方程组的方法,根据矩阵的特性选择合适的方法可以大大提高效率:
partialPivLu():部分主元LU分解,适用于可逆方阵fullPivLu():完全主元LU分解,更稳定但更慢householderQr():QR分解colPivHouseholderQr():带列主元的QR分解,更稳定fullPivHouseholderQr():完全主元QR分解,最稳定但最慢ldlt():LDLT分解,适用于正定或负定矩阵llt():Cholesky分解,仅适用于正定矩阵,但最快
特征值和特征向量计算
特征值问题在许多领域都非常重要,比如主成分分析、振动分析等。Eigen(顾名思义)当然也提供了计算特征值和特征向量的功能:
#include <iostream>
#include <Eigen/Dense>
int main() {
Eigen::Matrix2d A;
A << 1, 2,
2, 3;
// 计算特征值和特征向量
Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> solver(A);
std::cout << "特征值:\n" << solver.eigenvalues() << std::endl;
std::cout << "特征向量:\n" << solver.eigenvectors() << std::endl;
return 0;
}
输出:
特征值:
-0.236068
4.23607
特征向量:
-0.851225 -0.524731
0.524731 -0.851225
注意,这里使用的SelfAdjointEigenSolver是专门为对称矩阵设计的求解器。如果矩阵不对称,你应该使用EigenSolver或ComplexEigenSolver。
高级功能简介
Eigen的功能远不止基础的矩阵运算,它还提供了许多高级功能:
1. 块操作
Eigen允许你轻松地操作矩阵的子块:
Eigen::MatrixXd m = Eigen::MatrixXd::Random(4, 4);
// 提取子矩阵
Eigen::MatrixXd topLeft = m.topLeftCorner(2, 2);
Eigen::MatrixXd bottomRight = m.bottomRightCorner(2, 2);
// 使用block方法
Eigen::MatrixXd block = m.block(1, 1, 2, 2); // 从(1,1)开始的2x2块
// 单行或单列
Eigen::VectorXd row = m.row(1); // 第2行
Eigen::VectorXd col = m.col(2); // 第3列
2. 矩阵分解
除了前面提到的用于求解方程的分解方法,Eigen还支持其他重要的矩阵分解:
// SVD分解
Eigen::MatrixXd A = Eigen::MatrixXd::Random(3, 2);
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV);
// 获取奇异值、U矩阵和V矩阵
Eigen::VectorXd singularValues = svd.singularValues();
Eigen::MatrixXd U = svd.matrixU();
Eigen::MatrixXd V = svd.matrixV();
3. 几何模块
Eigen还有一个专门的几何模块,用于处理旋转、平移等变换:
#include <Eigen/Geometry>
// 创建一个3D旋转(绕Z轴旋转45度)
Eigen::AngleAxisd rotation(M_PI/4, Eigen::Vector3d::UnitZ());
// 转换为旋转矩阵
Eigen::Matrix3d rotMatrix = rotation.toRotationMatrix();
// 应用到一个向量
Eigen::Vector3d v(1, 0, 0);
Eigen::Vector3d rotated = rotation * v;
4. 稀疏矩阵
对于稀疏矩阵(大部分元素为零的矩阵),Eigen提供了专门的稀疏模块:
#include <Eigen/Sparse>
// 创建一个稀疏矩阵
Eigen::SparseMatrix<double> sparseMatrix(1000, 1000); // 1000x1000的稀疏矩阵
// 填充一些元素
sparseMatrix.insert(0, 0) = 1.0;
sparseMatrix.insert(1, 2) = 3.5;
sparseMatrix.insert(999, 999) = 2.0;
// 压缩以优化存储
sparseMatrix.makeCompressed();
// 使用专门的稀疏求解器
Eigen::SparseLU<Eigen::SparseMatrix<double>> solver;
solver.compute(sparseMatrix);
性能优化技巧
使用Eigen时,有一些技巧可以帮助你获得最佳性能:
- 预先分配内存:对于动态大小的矩阵,预先分配内存可以避免重新分配
// 不好的方式
Eigen::MatrixXd result;
result = A * B; // 可能导致额外的内存分配
// 更好的方式
Eigen::MatrixXd result(A.rows(), B.cols()); // 预先分配
result = A * B;
- 使用正确的求解器:根据矩阵的特性选择合适的求解器
// 对于正定矩阵,LLT分解最快
if (matrix.isPositiveDefinite()) {
x = matrix.llt().solve(b);
} else {
x = matrix.colPivHouseholderQr().solve(b);
}
- 避免显式转置大型矩阵:使用
transpositions和adjoint来避免实际转置操作
// 不好的方式
result = A.transpose() * b;
// 更好的方式
result = A.adjoint() * b; // 不会实际转置A
- 启用编译器优化:确保使用-O2或-O3编译标志
g++ -O3 -march=native my_program.cpp -o my_program
与其他库的集成
Eigen可以很好地与其他流行的C++库集成:
与OpenCV集成
#include <Eigen/Dense>
#include <opencv2/opencv.hpp>
// OpenCV矩阵转换为Eigen矩阵
cv::Mat cvMat(3, 3, CV_64F);
// ... 填充cvMat ...
Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>> eigenMat(
cvMat.ptr<double>(), cvMat.rows, cvMat.cols
);
// Eigen矩阵转换为OpenCV矩阵
Eigen::MatrixXd eigenMatrix(3, 3);
// ... 填充eigenMatrix ...
cv::Mat cvMatrix(3, 3, CV_64F, eigenMatrix.data());
与STL容器集成
#include <vector>
#include <Eigen/Dense>
// 从STL vector创建Eigen向量
std::vector<double> stdVector = {1.0, 2.0, 3.0};
Eigen::Map<Eigen::VectorXd> eigenVector(stdVector.data(), stdVector.size());
// 从Eigen向量创建STL vector
Eigen::VectorXd eigenVec(3);
eigenVec << 4.0, 5.0, 6.0;
std::vector<double> stdVec(eigenVec.data(), eigenVec.data() + eigenVec.size());
常见问题与解决方案
使用Eigen时可能会遇到一些常见问题,这里提供一些解决方案:
1. 编译错误:模板实例化太深
template instantiation depth exceeds maximum of 17
解决方案:避免复杂的表达式链,将复杂表达式分解为多个简单表达式。
2. 内存对齐问题
在某些平台上,如果矩阵未正确对齐,可能会出现崩溃。
解决方案:使用Eigen提供的对齐分配器:
std::vector<Eigen::Matrix4f, Eigen::aligned_allocator<Eigen::Matrix4f>> matrices;
3. 动态内存分配过多
对于性能关键型应用,过多的动态内存分配可能成为瓶颈。
解决方案:尽可能使用固定大小的矩阵,或预先分配动态矩阵的内存。
总结
Eigen库是一个功能强大的C++线性代数库,提供了丰富的数学工具和优化的性能。通过本教程,我们学习了:
- Eigen的基本介绍和安装方法
- 矩阵和向量的创建与操作
- 基本的线性代数运算
- 线性方程组求解和特征值计算
- 高级功能如块操作、矩阵分解和几何变换
- 性能优化技巧
- 与其他库的集成方法
无论你是数值分析专家、机器学习工程师,还是刚入门的编程爱好者,Eigen都能满足你的需求。它简洁的API和出色的性能使它成为C++数值计算的首选工具之一!
希望这篇教程对你有所帮助。现在,拿起键盘,开始你的Eigen之旅吧!学习任何库最好的方法就是——动手实践!!!
参考资源
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐
所有评论(0)