本文整理了科学计算包 Lapack 的安装过程和使用规范。
环境包
需要安装 gfortran 和 cmake
sudo apt-get install gfortran
BLAS 库和 CBLAS 接口
BLAS(basic linear algebra subroutine) 是一系列基本线性代数运算函数的接口(interface)标准。这里的线性代数运算是指例如矢量的线性组合,矩阵乘以矢量,矩阵乘以矩阵等。接口在这里指的是诸如哪个函数名实现什么功能,有几个输入和输出变量,分别是什么。
BLAS 被广泛用于科学计算和工业界,已成为业界标准。在更高级的语言和库中,即使我们不直接使用 BLAS 接口,它们也是通过调用 BLAS 来实现的(如 Matlab 中的各种矩阵运算)。
BLAS 原本是用 Fortran 语言写的,但后来也产生了 C 语言的版本 cBLAS , 接口与 Fortran 的略有不同(例如使用指针传递数组),但大同小异。
注意 BLAS 是一个接口的标准而不是某种具体实现(implementation),简单来说,就是不同的作者可以各自写出不同版本的 BLAS 库, 实现同样的接口和功能,但每个函数内部的算法可以不同。这些不同导致了不同版本的 BLAS 在不同机器上运行的速度也不同。
BLAS 的官网是 Netlib ,可以浏览完整的说明文档以及下载源代码。这个版本的 BLAS 被称为 reference BLAS,运行速度较慢,通常被其他版本用于衡量性能。对于 Intel CPU 的计算机,性能最高的是 Intel 的 MKL (Math Kernel Library) 中提供的 BLAS 。 MKL 虽然不是一个开源软件,但目前可以免费下载使用。如果想要免费开源的版本,可以尝试 OpenBlas 或者 ATLAS2 ,另外,无论是否使用 MKL,BLAS 的文档都推荐看 MKL 的相关页面 。
安装 blas
wget http://www.netlib.org/blas/blas.tgz
tar -xzvf blas.tgz
cd BLAS-3.10.0
make
sudo cp blas_LINUX.a /usr/local/lib/libblas.a
# 将 blas_LINUX.a 复制到系统库中
# 也可以使用 ln -sf ~/BLAS-3.10.0/blas_LINUX.a /usr/local/lib/libblas.a 进行链接
安装 cblas
wget http://www.netlib.org/blas/blast-forum/cblas.tgz
tar -xavf cblas.tgz
cd CBLAS
cp Makefile.LINUX Makefile.in
# 修改 Makefile.in ,指出 libblas.a 的位置
# BLLIB = /usr/local/lib/libblas.a
make
sudo cp lib/cblas_LINUX.a /usr/local/lib/libcblas.a
# 将 cblas_LINUX.a 复制到系统库中
安装 LAPACK
由于 github 官网链接下载太慢,直接在 http://www.netlib.org/lapack/ 中寻找最新版本下载
tar -xzvf lapack-3.10.0.tar.gz
cd lapack-3.10.0
cp make.inc.example make.inc
# 修改其中 BLASLIB 和 CBLASLIB 路径
make lib
sudo cp liblapack.a /usr/local/lib/
sudo cp libtmglib.a /usr/local/lib/
# 将 liblapack.a 、libtmglib.a 复制到系统库中
cd TESTING
make # 编译 lapack 文件
cd LAPACKE # 进入 LAPACKE 文件夹
make # 编译 lapacke
cp include/*.h /usr/local/include/
# 复制全部头文件到系统头文件目录
cp .. # 返回上一级
cp *.a /usr/local/lib/
# 复制全部库文件到系统库目录
基本框架
概述
LAPACK API 支持两种形式:标准的 ANSI C 和标准的 FORTRAN
每个 LAPACK 例程都有四个形式:
精度 | 例程前缀 |
---|---|
REAL | S |
REAL DOUBLE | D |
COMPLEX | C |
COMPLEX DOUBLE | Z |
函数命名规则
- LAPACK 中的每个函数名已经说明了该函数的使用规则
- 所有函数都是以 XYYZZZ 的形式命名
- 对于某些函数,没有第六个字符,只是 XYYZZ 的形式
- X 代表数据类型(S D C Z),YY 代表数组的类型,ZZZ代表计算方法
注意:在新版中,使用重复迭代法的函数 DSGESV 和 ZCDESV 头两个字母表示使用的精度
DS 输入双精度,算法使用单精度
ZC 输入使用 DOUBLE COMPLEX,算法使用 COMPLEX 单精度
几种常用的数组类型
记号 | 类型 |
---|---|
DI (diagonal) | 对角阵 |
GB (general band) | 一般带状矩阵 |
GE (general) | 一般矩阵 |
GT (general tridiagonal) | 一般三对角阵 |
OR (real orthogonal) | 实正交阵 |
SB (real symmetric) | 实对称带状阵 |
ST (real symmetric tridiagonal) | 实对称三对角阵 |
SY (symmetric) | 对称阵 |
TB (triangularband) | 三角形带状矩阵 |
编译指令
使用 gfortran 编译,通过-l
导入静态库;导入静态库的顺序不能变
gfortran test.cpp -o test -llapacke -llapack -lblas
注意:此时程序必须使用 C 语言编写
- 链接 C++ 库
通过添加参数来调用 C++ 相关的库和使用新标准
gfortran test.cpp -o test -llapacke -llapack -lblas -lstdc++ -std=c++11 # 链接 C++ 标准库 + C++11 标准
- 链接 fortran 库
如果使用 g++ 进行编译,则需要链接 fortran 库
g++ test.cpp -o test -llapacke -llapack -lblas -lgfortran
另外,需要注意的是,由于高版本的 gcc 默认会在编译参数中添加 -fPIC 参数以实现相对路径的共享,因此可能导致编译时不能正确链接库,这时就需要添加 -no-pie 参数来取消 -fPIC 参数
g++ test.cpp -o test -llapacke -llapack -lblas -lgfortran -no-pie
- 使用 extern 修饰
extern void c_func1(float*, int); // 使用 C++ 的编译修饰规则,Fortran 无法调用
extern "C" int c_func2(); // 使用 C 的编译修饰规则,Fortran 可以调用
LAPACK语法
Lapack 函数手册:
http://www.netlib.org/lapack/single/
http://www.netlib.org/lapack/double/
http://www.netlib.org/lapack/complex/
http://www.netlib.org/lapack/complex16
通过 XYYZZZ 中部分类型的组合,我们可以得到不同的函数 LAPACK_XYYZZZ
区分例程
// Linear system, solve Ax = b
LAPACKE_XYYsv(); // 标准求解
LAPACKE_XYYsvx(); // 精确求解:包括 A'x = b,条件数,误差分析等
// Linear least squares, minimize ||b - Ax||2
LAPACKE_XYYls(); // A满秩,QR求解
参数格式
// 标准求解 Ax = B A = N x N, B = N x NRHS, X = N x NRHS
lapack_int LAPACKE_dgesv(
int matrix_layout, // 矩阵的格式
lapack_int n, // 方程组的行数,也即矩阵 A 的行数
lapack_int nrhs, // rhs: right-hand-side 右边矩阵的列数,也即 B 的列数
double* a, // 矩阵 A
lapack_int lda, // 数组 A 的主尺寸,这是存放矩阵 A 的数组的尺寸,不小于 N
lapack_int* ipiv, // 长为 N 的数组,用于定义置换矩阵 P,一般初始化为0即可
double* b, // 矩阵 B
lapack_int ldb // 数组 B 的主尺寸,这是存放矩阵 B 的数组的尺寸,不小于 N
);
//
matrix_layout
- LAPACK_ROW_MAJOR 按行求解(标准)
- LAPACK_COL_MAJOR 按列求解
之后介绍的 Lapack 函数中 matrix_layout 都会沿用这两个宏。
LAPACK 函数
degsv
求解一般实线性方程组 \(AX=B\) ,其中 \(A\in\mathbb{R}^{N\times N},\ X,B\in\mathbb{R}^{N\times NRHS}\) ,并且要求 \(A\) 可逆。求解过程使用列主元 \(LU\) 分解法,
\[A = PLU \]其中 \(P\) 是置换阵, \(L,U\) 分别为上下三角阵。
lapack_int LAPACKE_dgesv( int matrix_layout, lapack_int n, lapack_int nrhs,
double* a, lapack_int lda, lapack_int* ipiv,
double* b, lapack_int ldb );
参数:
- N - 线性方程的个数,也就是 \(A\) 的阶数
- NRHS - 矩阵 \(B\) 的列数
- A - 矩阵 \(A\) ,返回储存 \(A\) 的 \(LU\) 分解
- LDA - 数组 \(A\) 的行维数, \(LDA \ge \max(1,N)\)
- IPIV - 存放返回维数为 \(N\) 的置换向量,行 \(i\) 被替换为行 \(IPIV(i)\)
- B - 矩阵 \(B\)
- LDB - 数组 \(B\) 的行维数, \(LDB\ge \max(1,N)\) ,注意是数组 \(B\) ,如果是单列向量,行数是 1
返回 INFO 存放计算标识:
- 0 成功退出
-
< 0
若INFO = -i
,则第 \(i\) 个变量是一个不可接受的值 -
> 0
若INFO = i
,则 \(U(i,i)\) 为零,因式分解完成,但 \(U\) 不可逆
dgels
求解超定或欠定实线性方程组,涉及 \(A\in\mathbb{R}^{M\times N}\) 或者它的转置,使用 \(QR\) 或 \(LQ\) 分解,它假定 \(A\) 满秩。
lapack_int LAPACKE_dgels( int matrix_layout, char trans, lapack_int m,
lapack_int n, lapack_int nrhs, double* a,
lapack_int lda, double* b, lapack_int ldb );
可选参数 TRANS :
- 若
TRANS = 'N', m>= n
:求超定系统 \(\|B-AX\|\) 的最小解 - 若
TRANS = 'N', m<n
:求欠定系统的最小解 - 若
TRANS = 'T', m>= n
:求欠定系统 \(A^TX=B\) 的最小解 - 若
TRANS = 'T', m<n
:求超定系统 \(\|B-A^TX\|\) 的最小解
右边向量 \(b\) 和解向量 \(x\) 分别存放为 \(M\times NRHS\) 矩阵 \(B\) 和 \(N\times NRHS\) 矩阵 \(X\) 。
参数:
- TRANS - 如上
- M - 矩阵 \(A\) 的行数
- N - 矩阵 \(A\) 的列数
- NRHS - 矩阵 \(B\) 的列数
- A - 矩阵 \(A\) ,如果 \(M>=N\) ,则 \(A\) 由 \(QR\) 分解覆盖;否则 \(A\) 由 \(LQ\) 分解覆盖
- LDA - 数组 \(A\) 的行维数, \(LDA\ge\max(1,M)\)
- B - 矩阵 \(B\) ,情况比较复杂,只考虑
TRANS = 'N', m>=n
,此时第 1 到 n 行包含最小二乘向量,第 n+1 到 M 行每列的平方和给出残向量 \(B-AX\) 每列的平方和 - LDB - 数组 \(B\) 的行维数, \(LDB\ge \max(1,M,N)\)
dsyev
求解实对称矩阵 \(A\) 的所有特征值和对应的特征向量
lapack_int LAPACKE_dsyev( int matrix_layout, char jobz, char uplo, lapack_int n,
double* a, lapack_int lda, double* w );
参数:
- JOBZ - 若为
'N'
,表示只计算特征值;若为'V'
表示计算特征值和特征向量 - UPLO - 若为
'U'
,表示存放 \(A\) 的上三角部分;若为'L'
,表示存放 \(A\) 的下三角部分 - N - 矩阵 \(A\) 的阶数
- A - 矩阵 \(A\) ,如果 UPLO 为
'U'
,则 \(A\) 的前 \(N\times N\) 上三角部分存放在上三角部分(因为没有必要存放整个 \(A\) ),反之同理;如果 JOBZ 为'V'
,则 \(A\) 存放正交特征向量;否则根据 UPLO 返回 \(A\) 其部分,其它部分会被摧毁 - LDA - 数组 \(A\) 的行维数, \(LDA\ge\max(1,N)\)
- W - 返回维数为 \(N\) 的向量,升序存放特征值
dgees
求解实非对称矩阵 \(A\in\mathbb{R}^{n\times n}\) 的特征值,实 Schur 分解,以及 Schur 向量的矩阵,给出 Schur 分解 \(A = ZTZ^T\) ;也可以选择将对角线上的特征值排序,则 \(Z\) 的第一列就是对应特征值子空间的一个正交基。
lapack_int LAPACKE_dgees( int matrix_layout, char jobvs, char sort,
LAPACK_D_SELECT2 select, lapack_int n, double* a,
lapack_int lda, lapack_int* sdim, double* wr,
double* wi, double* vs, lapack_int ldvs );
参数:
- JOBVS - 若为
'N'
,则不计算 Schur 向量;若为'V'
则计算 - SORT - 若为
'N'
,则不对对角特征值排序;若为'S'
则排序 - SELECT - 参数过于复杂,当 SORT 为
'N'
,此参数不被引用 - N - 矩阵 \(A\) 的阶数
- A - 矩阵 \(A\) ,返回 Schur 标准型
- LDA - 数组 \(A\) 的行维数, \(LDA\ge\max(1,N)\)
- SDIM - 返回值,若 SORT 为
'N'
,返回 0 - WR - 返回 \(N\) 维数组
- WI - 返回 \(N\) 维数组,它们分别存放特征值的实部和虚部,共轭特征对会以虚部为正负的顺序连续出现
- VS - 返回 \(LDVS\times N\) 数组,若 JOBVS 为
'V'
,则其中包含 Schur 向量构成的正交阵 \(Z\) ;否则不被引用 - LDVS - 数组 VS 的行维数
dgecon
计算一般实矩阵 \(A\) 的条件数
lapack_int LAPACKE_dgecon( int matrix_layout, char norm, lapack_int n,
const double* a, lapack_int lda, double anorm,
double* rcond );
参数:
- NORM - 若为
'1'
表示 1 范数,为'I'
表示无穷范数 - N - 矩阵 \(A\) 的阶数
- A - 矩阵 \(A\)
- LDA - 数组 \(A\) 的行维数, \(LDA\ge\max(1,N)\)
- ANORM - 初始矩阵的范数
- RCOND - 返回
RCOND = 1/(norm(A) * norm(inv(A)))
dgehrd
通过正交变换 \(Q^TAQ =H\) 将一般实矩阵 \(A\) 化为上 Hessenberg 阵 \(H\) 。
lapack_int LAPACKE_dgehrd( int matrix_layout, lapack_int n, lapack_int ilo,
lapack_int ihi, double* a, lapack_int lda,
double* tau );
参数:
- N - 矩阵 \(A\) 的阶数
- ILO - 输入整型
- IHI - 输入整型;它们假设矩阵 \(A\) 已经在
1:ILO-1
行列和IHI+1:N
行列部分上三角化。只有当已经调用了 dgebal 后才会设置它们,否则请将它们分别设为 1 和 N - A - 矩阵 \(A\) ,返回上三角和次对角线为上 Hessenberg 化矩阵 \(H\) ,剩下的元素和 TAU 一起存放正交阵 \(Q\)
- LDA - 数组 \(A\) 的行维数, \(LDA\ge\max(1,N)\)
- TAU - 返回 \(N-1\) 维数组,存放变换阵的标量因子,具体解释如下:
/* Further Details
* ===============
*
* The matrix Q is represented as a product of (ihi-ilo) elementary
* reflectors
*
* Q = H(ilo) H(ilo+1) . . . H(ihi-1).
*
* Each H(i) has the form
*
* H(i) = I - tau * v * v**T
*
* where tau is a real scalar, and v is a real vector with
* v(1:i) = 0, v(i+1) = 1 and v(ihi+1:n) = 0; v(i+2:ihi) is stored on
* exit in A(i+2:ihi,i), and tau in TAU(i).
*
* The contents of A are illustrated by the following example, with
* n = 7, ilo = 2 and ihi = 6:
*
* on entry, on exit,
*
* ( a a a a a a a ) ( a a h h h h a )
* ( a a a a a a ) ( a h h h h a )
* ( a a a a a a ) ( h h h h h h )
* ( a a a a a a ) ( v2 h h h h h )
* ( a a a a a a ) ( v2 v3 h h h h )
* ( a a a a a a ) ( v2 v3 v4 h h h )
* ( a ) ( a )
*
* where a denotes an element of the original matrix A, h denotes a
* modified element of the upper Hessenberg matrix H, and vi denotes an
* element of the vector defining H(i).
*/
dgeqrf
计算一个实 \(M\times N\) 矩阵 \(A\) 的 \(QR\) 分解
lapack_int LAPACKE_dgeqrf( int matrix_layout, lapack_int m, lapack_int n,
double* a, lapack_int lda, double* tau );
参数:
- M - 矩阵 \(A\) 的行数
- N - 矩阵 \(A\) 的列数
- A - 矩阵 \(A\) ,返回上三角部分为 \(R\) ,其下的部分和 TAU 存放正交阵 \(Q\)
- LDA - 数组 \(A\) 的行维数, \(LDA\ge\max(1,M)\)
- TAU - 返回标量因子 \(\min(M,N)\) 维向量,具体解释如下:
/* Further Details
* ===============
*
* The matrix Q is represented as a product of elementary reflectors
*
* Q = H(1) H(2) . . . H(k), where k = min(m,n).
*
* Each H(i) has the form
*
* H(i) = I - tau * v * v**T
*
* where tau is a real scalar, and v is a real vector with
* v(1:i-1) = 0 and v(i) = 1; v(i+1:m) is stored on exit in A(i+1:m,i),
* and tau in TAU(i).
*/
dgetrf
计算一个实 \(M\times N\) 矩阵 \(A\) 的列主元 \(LU\) 分解
lapack_int LAPACKE_dgetrf( int matrix_layout, lapack_int m, lapack_int n,
double* a, lapack_int lda, lapack_int* ipiv );
参数:
- M - 矩阵 \(A\) 的行数
- N - 矩阵 \(A\) 的列数
- A - 矩阵 \(A\) ,返回存储 \(L,U\)
- LDA - 数组 \(A\) 的行维数, \(LDA\ge\max(1,M)\)
- IPIV - 返回 \(\min(M,N)\) 维向量,存放置换向量,行 \(i\) 被替换为行 \(IPIV(i)\)
dgetri
利用 \(LU\) 分解计算一个矩阵的逆,它先求 \(U\) 的逆,然后求解 \(A^{-1}L = U^{-1}\) 得到 \(A^{-1}\)
lapack_int LAPACKE_dgetri( int matrix_layout, lapack_int n, double* a,
lapack_int lda, const lapack_int* ipiv );
参数:
- N - 矩阵 \(A\) 的阶数
- A - 矩阵 \(A\) ,
- LDA - 数组 \(A\) 的行维数, \(LDA\ge\max(1,N)\)
- IPIV - 存放置换向量的 \(N\) 维数组
LAPACK实例
利用 LAPACK_dgels
求解最小二乘问题
#include <stdio.h>
#include <lapacke.h>
int main (int argc, const char * argv[])
{
double a[5*3] = {1,2,3,4,5,1,3,5,2,4,1,4,2,5,3};
double b[5*2] = {-10,12,14,16,18,-3,14,12,16,16};
lapack_int info,m,n,lda,ldb,nrhs;
int i,j;
m = 5;
n = 3;
nrhs = 2;
lda = 5;
ldb = 5;
info = LAPACKE_dgels(LAPACK_COL_MAJOR,'N',m,n,nrhs,a,lda,b,ldb);
for(i=0;i<n;i++)
{
for(j=0;j<nrhs;j++)
{
printf("%lf ",b[i+ldb*j]);
}
printf("\n");
}
return(info);
}