【Eigen教程】稀疏矩阵 (六)

第 6 章 稀疏矩阵

  •     稀疏矩阵操作

  •     压缩稀疏行(格式)

  •     求解稀疏线性系统

  •     无矩阵求解器

稀疏矩阵: 涵盖稀疏矩阵操作、压缩稀疏行格式、求解稀疏线性系统以及矩阵自由求解器四个部分,每个部分都附有详细的中文注释说明,所有示例均使用 C++ 语言,并利用了 Eigen 库(当然也可以不用 Eigen,只是这里统一采用 Eigen 进行说明)。


6.1 稀疏矩阵操作

在本节中,我们介绍如何利用 Eigen 构造、初始化和遍历稀疏矩阵。Eigen 中的稀疏矩阵主要采用 Triplet 列表方式进行初始化,下面的例子中构造了一个 5×5 的稀疏矩阵,并通过迭代器输出其中的非零元素。

#include <iostream>
#include <vector>
#include <Eigen/Sparse>  // 引入 Eigen 稀疏矩阵模块

usingnamespace Eigen;
usingnamespace std;

intmain(){
    // 定义一个 5x5 的稀疏矩阵,元素类型为 double
    SparseMatrix<double>spMat(5,5);
    
    // 使用 Triplet 列表存储非零元素
    typedef Triplet<double> T;
    std::vector<T> tripletList;
    
    // 向 tripletList 中添加非零元素,格式为 Triplet<行, 列, 数值>
    tripletList.push_back(T(0,0,10));
    tripletList.push_back(T(1,2,20));
    tripletList.push_back(T(2,4,30));
    tripletList.push_back(T(3,1,40));
    tripletList.push_back(T(4,3,50));
    
    // 利用 tripletList 构造稀疏矩阵
    spMat.setFromTriplets(tripletList.begin(), tripletList.end());
    
    // 遍历稀疏矩阵中所有的非零元素
    // 注意:如果 spMat 是列主序(默认),outerSize() 返回的是列数;如果是行主序,则返回行数
    for(int k =0; k < spMat.outerSize();++k){
        // 内部迭代器遍历第 k 列(或第 k 行,取决于存储方式)上的所有非零元素
        for(SparseMatrix<double>::InnerIterator it(spMat, k); it;++it){
            // 输出元素的行号、列号以及对应的数值
            cout <<"A("<< it.row()<<","<< it.col()<<") = "<< it.value()<< endl;
        }
    }
    
    return0;
}

6.2 压缩稀疏行(CSR)格式

压缩稀疏行格式(CSR)通常使用行主序存储。Eigen 默认是列主序,但我们可以通过指定存储方式为 RowMajor 来实现行主序存储。下面的示例展示如何构造一个行主序的稀疏矩阵,并访问其底层的 CSR 数据结构(行指针、列索引、非零值数组)。

#include <iostream>
#include <vector>
#include <Eigen/Sparse>  // 引入稀疏矩阵模块

usingnamespace Eigen;
usingnamespace std;

intmain(){
    // 定义一个 5x5 的行主序稀疏矩阵,RowMajor 表示按行存储(符合 CSR 格式)
    SparseMatrix<double, RowMajor>spMat(5,5);
    
    // 同样使用 Triplet 列表来初始化非零元素
    typedef Triplet<double> T;
    std::vector<T> tripletList;
    tripletList.push_back(T(0,0,10));
    tripletList.push_back(T(1,2,20));
    tripletList.push_back(T(2,4,30));
    tripletList.push_back(T(3,1,40));
    tripletList.push_back(T(4,3,50));
    
    spMat.setFromTriplets(tripletList.begin(), tripletList.end());
    
    // Eigen 中行主序稀疏矩阵底层存储即为 CSR 格式,下面获取其内部数据:
    // outerIndexPtr() 返回行指针数组(每一行非零元素在内存中的起始位置索引)
    // innerIndexPtr() 返回列索引数组(存储每个非零元素的列索引)
    // valuePtr() 返回非零元素数组(存储每个非零元素的具体数值)
    
    cout <<"outerIndexPtr (行指针数组): ";
    for(int i =0; i <= spMat.rows();++i){
        cout << spMat.outerIndexPtr()[i]<<" ";
    }
    cout << endl;
    
    cout <<"innerIndexPtr (列索引数组): ";
    for(int i =0; i < spMat.nonZeros();++i){
        cout << spMat.innerIndexPtr()[i]<<" ";
    }
    cout << endl;
    
    cout <<"valuePtr (非零值数组): ";
    for(int i =0; i < spMat.nonZeros();++i){
        cout << spMat.valuePtr()[i]<<" ";
    }
    cout << endl;
    
    return0;
}

6.3 求解稀疏线性系统

在本节中,我们介绍如何使用 Eigen 的稀疏矩阵求解器来求解线性系统 Ax=b
(此处假设 A 为对称正定矩阵)。我们采用 SimplicialLLT 分解方法进行求解。

#include <iostream>
#include <vector>
#include <Eigen/Sparse>         // 引入稀疏矩阵模块
#include <Eigen/SparseCholesky> // 引入稀疏 Cholesky 分解模块

usingnamespace Eigen;
usingnamespace std;

intmain(){
    // 定义一个 4x4 的对称正定稀疏矩阵 A
    SparseMatrix<double>A(4,4);
    typedef Triplet<double> T;
    std::vector<T> tripletList;
    
    // 构造一个简单的对称正定矩阵 A:
    // 例如: A = [ 4  -1   0   0
    //              -1   4  -1   0
    //               0  -1   4  -1
    //               0   0  -1   4 ]
    tripletList.push_back(T(0,0,4));
    tripletList.push_back(T(0,1,-1));
    tripletList.push_back(T(1,0,-1));
    tripletList.push_back(T(1,1,4));
    tripletList.push_back(T(1,2,-1));
    tripletList.push_back(T(2,1,-1));
    tripletList.push_back(T(2,2,4));
    tripletList.push_back(T(2,3,-1));
    tripletList.push_back(T(3,2,-1));
    tripletList.push_back(T(3,3,4));
    
    // 利用 tripletList 初始化矩阵 A
    A.setFromTriplets(tripletList.begin(), tripletList.end());
    
    // 定义右侧向量 b
    VectorXd b(4);
    b <<15,10,10,15;
    
    // 使用 SimplicialLLT 进行稀疏 Cholesky 分解,求解 Ax = b
    SimplicialLLT<SparseMatrix<double>> solver;
    solver.compute(A);
    if(solver.info()!= Success){
        cout <<"矩阵分解失败"<< endl;
        return-1;
    }
    
    // 求解线性系统
    VectorXd x = solver.solve(b);
    if(solver.info()!= Success){
        cout <<"求解失败"<< endl;
        return-1;
    }
    
    // 输出求得的解向量 x
    cout <<"解向量 x = \n"<< x << endl;
    
    return0;
}

6.4 矩阵自由求解器

矩阵自由求解器(Matrix Free Solvers)通常不需要显式存储整个矩阵,而是通过定义矩阵-向量乘法操作来实现迭代求解。下面给出一个共轭梯度(Conjugate Gradient, CG)方法的示例,其中矩阵 A 仅通过一个函数 A_mul 实现其与向量的乘法。

d548cecef1ccd747403aa55256a6f58a.png

c878d583585e7ef670814aad36c1f6cf.png

#include <iostream>
#include <Eigen/Dense>  // 引入 Eigen Dense 模块用于向量操作
#include <cmath>        // 用于数学函数 sqrt

usingnamespace Eigen;
usingnamespace std;

// 定义一个矩阵向量乘法函数,计算 y = A * x
// 本例中 A 定义为上述 4x4 矩阵
voidA_mul(const VectorXd &x, VectorXd &y){
    int n = x.size();
    y.resize(n);
    y.setZero();// 将 y 初始化为零向量
    for(int i =0; i < n;++i){
        // 计算对角线元素的贡献
        y(i)+=4*x(i);
        // 如果存在左边元素,则添加对应的乘积(下三角元素)
        if(i -1>=0){
            y(i)+=-1*x(i -1);
        }
        // 如果存在右边元素,则添加对应的乘积(上三角元素)
        if(i +1< n){
            y(i)+=-1*x(i +1);
        }
    }
}

// 矩阵自由的共轭梯度求解器
// 参数:b 为右侧向量,tol 为收敛容限,maxIter 为最大迭代次数
VectorXd matrixFreeCG(const VectorXd &b,double tol =1e-6,int maxIter =1000){
    int n = b.size();
    // 初始猜测解设为零向量
    VectorXd x =VectorXd::Zero(n);
    // 初始残差 r = b - A*x,因 x = 0 所以 r = b
    VectorXd r = b;
    // 初始搜索方向设为 r
    VectorXd p = r;
    // 计算初始残差平方
    double rsold = r.dot(r);
    
    for(int i =0; i < maxIter;++i){
        VectorXd Ap(n);
        A_mul(p, Ap);// 计算 A 乘以搜索方向 p
        
        // 计算步长 alpha = (r^T * r) / (p^T * A * p)
        double alpha = rsold / p.dot(Ap);
        
        // 更新解向量: x = x + alpha * p
        x = x + alpha * p;
        // 更新残差: r = r - alpha * A * p
        r = r - alpha * Ap;
        
        // 计算新的残差平方
        double rsnew = r.dot(r);
        // 检查是否收敛,如果残差的 2 范数小于 tol,则认为已收敛
        if(sqrt(rsnew)< tol){
            cout <<"共轭梯度法在 "<< i +1<<" 次迭代后收敛."<< endl;
            break;
        }
        
        // 更新搜索方向: p = r + (rsnew/rsold) * p
        p = r +(rsnew / rsold)* p;
        // 更新 rsold
        rsold = rsnew;
    }
    return x;
}

intmain(){
    int n =4;
    // 定义右侧向量 b
    VectorXd b(n);
    b <<15,10,10,15;
    
    // 使用矩阵自由共轭梯度求解器求解 Ax = b
    VectorXd x =matrixFreeCG(b);
    
    // 输出求得的解向量 x
    cout <<"解向量 x = \n"<< x << endl;
    
    return0;
}

说明

  • 矩阵自由求解器思想

    :与传统方法需要先构造并存储整个稀疏矩阵不同,此方法仅需定义一个“矩阵-向量乘法”操作函数 A_mul,适用于大规模问题或隐式定义的算子。

  • 共轭梯度法

    :适用于对称正定矩阵,在每次迭代中仅需一次矩阵向量乘法,非常适合矩阵自由的求解场景。


以上便是第6章关于稀疏矩阵的完整教程。每个代码示例都配有详细的中文注释,帮助读者理解稀疏矩阵的构造、存储格式以及如何利用不同的求解器求解稀疏线性系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值