liblcg/README.md
2024-09-11 13:39:28 +08:00

225 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# C++ Library of the Linear Conjugate Gradient Methods (LibLCG) 说明文档
张壹yizhang-geo@zju.edu.cn
_浙江大学地球科学学院·地球物理研究所_
**此说明仅覆盖算法库的简单介绍及使用,更详细的内容请查看代码注释。如果还有问题,请发邮件联系我。同时也欢迎有兴趣的同学加入开发团队!**
## 简介
liblcg 是一个高效的、可扩展的 C++ 线性共轭梯度算法库在原生数据结构接口的基础上同时提供基于Eigen3和CUDA的算法接口可以方便的实现基于CPU或GPU并行的加速计算其中基于Eigen3的算法包含了稠密与稀疏矩阵的实现而基于CUDA的算法主要为稀疏矩阵的实现。liblcg 包含多种实数与复数域的共轭梯度算法与其他一些迭代求解方法。目前已有得方法包括共轭梯度法、预优的共轭梯度算法、共轭梯度平方算法、双稳共轭梯度算法、BB步共轭梯度投影法与SPG共轭梯度投影法复数域的双共轭梯度法、共轭梯度平方法、预优的共轭梯度法与TFQMR法。共轭梯度法广泛应用于无约束与不等式约束的线性最优化问题拥有优良的收敛与计算效率。
共轭梯度算法可用于求解如下形式的线性方程组:
```
Ax = B
```
其中A 是一个 N 阶的方阵、x 为 N\*1 大小的待求解的模型向量B 为 N\*1 大小的需拟合的目标向量。需要注意的是不同种类的共轭梯度算法对A可能有不同的要求比如必须是正定的或者对称的。不同算法的具体要求可以查阅其他参考文献或者查看代码中的注释。
## 安装
算法库使用 CMake 工具进行汇编可在不同操作平台生成相应的Makefile或工程文件。
### 编译选项
算法库目前可用的编译选项有:
* LibLCG_OPENMP是否使用OpenMP进行加速需要安装OpeMP。默认为ON。
* LibLCG_EIGEN是否编译基于Eigen的算法与借口需要安装Eigen。默认为ON。
* LibLCG_STD_COMPLEX是否使用std::complex\<double\>作为复数的默认类型。默认为ON。
* LibLCG_CUDA是否编译基于CUDA的算法与借口需要安装CUDA。默认为ON。
用户可以使用cmake命令中的-D选项对编译选项进行设置比如关闭LibLCG_Eigen
```shell
cmake -DLibLCG_EIGEN=OFF
```
### Linux 与 MacOS
liblcg的默认安装路径为 /usr/local。头文件与动态库分别安装于 include 与 lib 文件夹。具体的编译与安装步骤如下:
1. 下载安装CMake软件
2. 下载安装GCC编译器常见系统已内置
3. 在源文件路径内使用如下命令进行编译与安装:
```shell
mkdir build && cd build && cmake .. && make install
```
### Windows
#### MinGW 和 GCC
Windows系统不包含GNU编译环境用户需自行下载并配置。方法如下
1. 下载MinGW安装文件并选择gcc、pthreads与make相关软件包安装
2. 下载安装CMake软件
3. 添加CMake与MinGW可执行文件路径至Windows环境变量
4. 在源文件路径内使用如下命令进行编译与安装:
```shell
mkdir build && cd build && cmake .. -G "MinGW Makefiles" && make install
```
默认的安装路径为C:/Program\\ Files。头文件与动态库分别安装于 include 与 lib 文件夹。
**注意:用户需要手动添加头文件与动态库地址到计算机的环境变量中。**
#### Visual Studio
用户可使用CMake工具构建VS工程文件并编译使用动态库。方法如下
1. 下载安装 Visual Studio 软件;
2. 下载安装CMake软件
3. 在源文件路径内使用如下命令生成VS工程文件
```shell
mkdir build && cd build && cmake .. -G "Visual Studio 16 2019"
```
_注如需生成其他版本的VS工程文件请使用-G命令查看相应的识别码。_
4. 使用 Visual Studio 打开.sln工程文件并编译动态库。
## 使用与编译
用户使用库函数时需在源文件中引入相应的头文件,如:
```cpp
#include "lcg/lcg.h"
```
编译可执行文件时需链接lcg动态库。以g++为例:
```shell
g++ example.cpp -llcg -o example_out
```
## 快速开始
要使用liblcg求解线性方程组Ax=B用户需要定义Ax乘积的计算函数回调函数该函数的功能为计算不同的x所对应的乘积Ax。以实数类型的共轭梯度算法为例其回调函数的接口定义为
```cpp
typedef void (*lcg_axfunc_ptr)(void* instance, const lcg_float* x, lcg_float* prod_Ax, const int n_size);
```
其中,`x`为输入的向量,`prod_Ax`为返回的乘积向量,`n`为这两个向量的长度。注意此处参数列表中并不包含矩阵A这意味这A必须为全局或者类变量。这样设计的主要原因是在某些复杂最优化问题的编程中计算并存储A并不实际或者划算此时一般采用的策略是存储相关变量且仅计算Ax的乘积所以矩阵A并不总是存在。
用户在定义Ax计算函数后即可调用求解函数 lcg_solver() 对线性方程组进行求解。以无约束的求解函数为例,其声明如下:
```cpp
int lcg_solver(lcg_axfunc_ptr Afp, lcg_progress_ptr Pfp, lcg_float* m, const lcg_float* B, const int n_size,
const lcg_para* param, void* instance, lcg_solver_enum solver_id = LCG_CGS);
```
其中:
1. `lcg_axfunc_ptr Afp` 为正演计算的回调函数;
2. `lcg_progress_ptr Pfp` 监控迭代过程的回调函数(非必须,无需监控时使用 nullptr 参数即可);
3. `lcg_float* m` 初始解向量,迭代取得的解也保存与此数组;
4. `const lcg_float* B` Ax = B 中的 B 项;
5. `const int n_size` 解向量的大小;
6. `const lcg_para* param` 迭代使用的参数,此参数为 nullptr 即使用默认参数;
7. `void* instance` 传入的实例对象, 此函数在类中使用即为类的 this 指针, 在普通函数中使用时即为 nullptr
8. `int solver_id` 求解函数使用的求解方法,具体的方法代号可查看对应的头文件;
### 一个简单的例子
```cpp
#include "cmath"
#include "iostream"
#include "lcg/lcg.h"
#define M 100
#define N 80
// 返回两个数组元素之间的最大差值
lcg_float max_diff(const lcg_float *a, const lcg_float *b, int size)
{
lcg_float max = -1;
for (int i = 0; i < size; i++)
{
max = lcg_max(sqrt((a[i] - b[i])*(a[i] - b[i])), max);
}
return max;
}
// 普通二维数组做核矩阵
lcg_float **kernel;
// 中间结果数组
lcg_float *tmp_arr;
// 计算核矩阵乘向量的乘积 lcg_solver的回调函数
void CalAx(void* instance, const lcg_float* x, lcg_float* prod_Ax, const int n_s)
{
// 注意核矩阵实际为 kernel^T * kernel大小为N*N
lcg_matvec(kernel, x, tmp_arr, M, n_s, MatNormal); // tmp_tar = kernel * x
lcg_matvec(kernel, tmp_arr, prod_Ax, M, n_s, MatTranspose); // prod_Ax = kernel^T * tmp_tar
return;
}
// 定义监控函数 lcg_solver的回调函数
// 这个函数显示当前的迭代次数与收敛值
int Prog(void* instance, const lcg_float* m, const lcg_float converge, const lcg_para* param, const int n_s, const int k)
{
std::clog << "\rIteration-times: " << k << "\tconvergence: " << converge;
return 0;
}
int main(int argc, char const *argv[])
{
// 开辟数组空间
kernel = lcg_malloc(M, N);
tmp_arr = lcg_malloc(M);
// 为核矩阵赋初值
lcg_vecrnd(kernel, -1.0, 1.0, M, N);
// 生成一组理论解
lcg_float *fm = lcg_malloc(N);
lcg_vecrnd(fm, 1.0, 2.0, N);
// 计算共轭梯度B项
lcg_float *B = lcg_malloc(N);
lcg_matvec(kernel, fm, tmp_arr, M, N, MatNormal);
lcg_matvec(kernel, tmp_arr, B, M, N, MatTranspose);
// 设置共轭梯度参数
lcg_para self_para = lcg_default_parameters();
self_para.epsilon = 1e-5;
self_para.abs_diff = 0;
// 声明一组解
lcg_float *m = lcg_malloc(N);
lcg_vecset(m, 0.0, N);
// 使用标准共轭梯度方法LCG_CG求解线性方程组
// 将回调函数传递给solver
// 由于回调函数为全局函数因此instance变量的值为NULL
int ret = lcg_solver(CalAx, Prog, m, B, N, &self_para, NULL, LCG_CG);
std::clog << std::endl; lcg_error_str(ret);
std::clog << "maximal difference: " << max_diff(fm, m, N) << std::endl << std::endl;
// 销毁数组
lcg_free(kernel, M);
lcg_free(tmp_arr);
lcg_free(fm);
lcg_free(B);
lcg_free(m);
return 0;
}
```
**完整的例子储存在[sample](src/sample)文件夹内。**
## 类模版
liblcg为不同类型的共轭梯度算法定义了通用的求解类模版包含了类中函数的指针代理及通用的监控函数实现用户可直接继承并使用。需要注意的是这些类模版中定义了纯虚的函数接口用户需要全部实现。其中没用到的定义成空函数就行了。以实数的求解类模版为例需要实现的接口函数包括
```cpp
void AxProduct(const lcg_float* a, lcg_float* b, const int num) = 0
void MxProduct(const lcg_float* a, lcg_float* b, const int num) = 0
```
其中`AxProduct`是Ax的计算函数`MxProduct`是预优过程的计算函数即M^-1x。