在上一篇中我们非常简要地介绍了 ScaLAPACK 软件。虽然 ScaLAPACK 在设计上作了很多工作使其方法接口与 LAPACK 尽量保持一致,但是直接使用 Fortran 或 C 语言按照上一篇中介绍的步骤使用 ScaLAPACK 仍然是一件比较麻烦和容易出错的事情,就好比我们使用 numpy.linalg 或 scipy.linalg (在底层调用 BLAS 和 LAPACK)中的相关函数比直接调用 BLAS 和 LAPACK 中的相关例程要容易和方便的多,我们也希望使用一个 Python 包装之后的 ScaLAPACK,下面我们就将介绍这样一个工具 scalapy。
为什么使用 scalapy
numpy.linalg 或 scipy.linalg 中提供了非常丰富且易用的线性代数计算函数,能够满足我们大多数的线性代数运算需求,为什么还要使用 ScaLAPACK 和 scalapy 呢?对规模不是很大的线性代数问题一般没必要使用 ScaLAPACK 和 scalapy,但是对比较大规模甚至很大规模的线性代数问题,比如超过 5000 × 5000 的矩阵运算,ScaLAPACK 和 scalapy 的优势就能显现了。对如此大规模的线性代数问题,你会发现 numpy.linalg 或 scipy.linalg 中的函数可能会计算的比较慢,因为 numpy.linalg 或 scipy.linalg 都不是分布式内存的计算工具,因此其只能利用单台机器的计算能力。同时,受制于单台机器可用内存的大小,所能解决问题的规模也会受到限制,不然就会超过内存的大小而导致程序崩溃。另外如果 numpy.linalg 或 scipy.linalg 使用的是 32 位的 LAPACK/BlAS 库,即使单台机器的内存充足,所能计算的问题的规模也会受到限制,比如矩阵元素的数目不能超过一个 32 位整型数所能表示的最大整数 2147483647,因此可计算的最大方块矩阵大小为 46340 × 46340,超过就会出错。对这些情况,ScaLAPACK 和 scalapy 就能帮上忙了,因为它们是分布式内存的计算工具,因此可以把计算分布到多台机器(如集群或超级计算机上),以充分利用多台机器的强大计算能力和更大的总内存空间来加快问题的求解或者解决某些在单台机器上无法完成的计算任务。
下载与安装 scalapy
软件依赖
- MPI 实现,目前支持 OpenMPI,IntelMPI 和 MPICH;
- ScaLAPACK 库, Netlib 和 Intel MKL 的 ScaLAPACK 都支持;
- Python 2.7, 3.2 或更新版本;
- numpy;
- mpi4py;
- Cython;
- f2py (可单独安装,或使用较新版本的 numpy 中包含的 f2py)。
安装
前往 https://github.com/jrs65/scalapy 下载 scalapy。可以直接下载 .zip 格式的压缩包,并解压,或者使用 git 将该软件 clone 到本地:
$ git clone https://github.com/jrs65/scalapy.git
进入软件顶层目录,使用以下命令进行安装:
$ python setup.py install [--user]
主要方法接口
scalapy 的核心成分 —— core 模块
core 模块提供了 1 个全局函数 initmpi 和 4 个类 ProcessContext,DistributedMatrix,ScalapyException 和 ScalapackException,下面分别进行介绍。
函数 initmpi
全局的 scalapy 初始化函数,一般会首先调用。
initmpi(gridshape, block_shape=[32, 32])
使用 scalapy 之前的初始化工作,会初始化一个全局的进程网格上下文 _context (为我们下面将要介绍的 ProcessContext 对象),和一个全局的块状循环分布的块大小 _block_shape。gridshape
是一个二元数组指定初始化的进程网格的大小为 gridshape[0] × gridshape[1], block_shape
也是一个二元数组指定全局块状循环分布的块大小,一般设置成 [16, 16],[32, 32] 或 [64, 64] 能获得比较高的计算性能,块的行和列也可以设置成不同大小。
注意:调用 initmpi 初始化的全局 ProcessContext 对象的通信子为 mpi4py.MPI.COMM_WORLD,如果需要使用其它的通信子,则必须手动使用(下面将会介绍的) ProcessContext 的构造函数来初始化一个 ProcessContext 对象。
类 ProcessContext
ProcessContext 对象存储有关 MPI/BLACS 进程的相关信息,这些信息会被(后面将会介绍的) DistributedMatrix 所使用。
方法
__init__(self, grid_shape, comm=None)
构造函数,以一个指定的进程网格大小 grid_shape
和一个 MPI 通信子 comm
创建一个 ProcessContext 对象。grid_shape
是一个二元数组指定初始化的进程网格的大小为 grid_shape[0] × grid_shape[1], comm
如果为 None,则会使用 mpi4py.MPI.COMM_WORLD。
属性
grid_shape
所创建的进程网格的 shape,为一个二元 tuple。
grid_position
本进程在进程网格中的位置,为一个二元 tuple。
mpi_comm
此 ProcessContext 对象所使用的 MPI 通信子。
blacs_context
BLACS context 句柄。
all_grid_positions
一个 shape 为 (mpi_comm_size, 2) 的数组,其第 i 行是第 i 个进程在进程网格中的位置。
all_mpi_ranks
一个 shape 为 (grid_shape[0], grid_shape[1]) 的整数数组,all_mpi_ranks[i, j] 给出进程网格位置 (i, j) 上的进程的 rank。
类 DistributedMatrix
一个以块状循环的方式分布在多个 MPI 进程组成的一个进程网格上的分布式矩阵。块状循环分布矩阵是 ScaLAPACK 相关例程内部所使用的数据分布形式。
DistributedMatrix 分布在各个进程上的数据为基本的 numpy (2维)数组,下面是 DistributedMatrix 中使用的 ScaLAPACK 数据类型与 numpy 类型和 mpi4py 类型的对照表:
numpy 类型 | MPI 类型 | ScaLAPACK 类型 | 说明 |
---|---|---|---|
np.float32 | MPI.FLOAT | 'S' | 单精度浮点数 |
np.float64 | MPI.DOUBLE | 'D' | 双精度浮点数 |
np.complex64 | MPI.COMPLEX | 'C' | 单精度复数 |
np.complex128 | MPI.COMPLEX16 | 'Z' | 双精度复数 |
DistributedMatrix 提供了类似 numpy 数组的切片操作,但不同于 numpy 数组的是,DistributedMatrix 的一个切片会完全复制原分布矩阵对应的数据而创建一个新的 DistributedMatrix,而不是返回原矩阵对应数据的一个视图。
主要方法
def __init__(self, global_shape, dtype=np.float64, block_shape=None, context=None)
构造函数,初始化一个空的 DistributedMatrix。global_shape
为所创建的 DistributedMatrix 的整体矩阵 shape,dtype
为其数据类型,目前只支持 np.float32、np.float64、np.complex64 和 np.complex128,block_shape
是块状循环分布的块的大小,如果为 None,会使用由 initmpi 函数所初始化的全局 _block_shape,context
为一个 ProcessContext 对象,如果为 None,则会使用由 initmpi 函数所初始化的全局 _context。
empty_like(cls, mat)
创建并返回一个与 DistributedMatrix mat
具有同样属性的空 DistributedMatrix。
empty_trans(cls, mat)
创建并返回一个 global_shape 为 DistributedMatrix mat
的转置(即交换其行和列)的 global_shape,但其它属性相同的空 DistributedMatrix。
identity(cls, n, dtype=np.float64, block_shape=None, context=None)
创建并返回一个 global_shape 为 (n
, n
) 的单位矩阵(即其对角元素全为 1.0,其它元素都为 0)。参数 dtype
,block_shape
和 context
与 __init__ 中的对应参数同。
copy(self)
返回当前 DistributedMatrix 的一个(深)复制,即每个进程的本地子数据都会进行复制。
row_indices(self)
返回本进程所持有的数据在当前 DistributedMatrix 中整体的行指标。
col_indices(self)
返回本进程所持有的数据在当前 DistributedMatrix 中整体的列指标。
indices(self, full=True)
返回本进程所持有的数据在当前 DistributedMatrix 中整体的行和列指标(一个由 numpy 数组组成的二元 tuple)。full
如果为 True,则会返回行和列广播后的矩阵,如果为 False,则只返回行和列的指标数组,其结果类似于 numpy.mgrid 和 numpy.ogrid 的区别。
可以使用其返回值非常容易地创建依赖于元素坐标的 DistributedMatrix,比如创建一个整体为 Mij = i + j 的 DistributedMatrix,可以这样操作:
...
dm = DistributedMatrix((100, 100))
rows, cols = dm.indices()
dm.local_array[:] = rows + cols
local_diagonal_indices(self, allow_non_square=False)
返回一个由 1d numpy 数组组成的三元 tuple (global_index, local_row_index, local_column_index) 以表明本进程所持有的子数据位于 (local_row_index, local_column_index) 的元素是整体矩阵的第 global_index 行(或)列的对角元素。allow_non_square
指明是否必须是方块矩阵。
可以使用其返回值非常容易地操作该 DistributedMatrix 的对角项,比如要完成运算 Mij += i2 * δij,可以这样操作:
...
dm = DistributedMatrix((100, 100))
gi, lri, lci = dm.local_diagonal_indices()
dm.local_array[lri, lci] += gi**2
trace(self)
返回矩阵的迹(即对角元素的和),每个进程都会返回同样的结果。
from_global_array(cls, mat, rank=None, block_shape=None, context=None)
由一个整体的矩阵 mat
创建并返回一个 DistributedMatrix。mat
为一个 2d numpy 数组,rank
如果为 None,假定每个进程都拥有相同的 mat
,故只需从进程本地的 mat
中提取对应的数据,否则只会从 rank
指定的进程的 mat
中提取数据并分发给给个进程,以创建一个 DistributedMatrix。参数 block_shape
和 contex
同 initmpi 中对应的参数。
to_global_array(self, rank=None)
将该 DistributedMatrix 转换为一个整体的 numpy 数组并返回。如果 rank
为 None,则所以进程都会返回相同的整体数组,否则只有 rank
对应的进程会返回整体数组,其它进程返回 None。
from_file(cls, filename, global_shape, dtype, block_shape=None, context=None, order='F', displacement=0)
从指定文件 filename
中读取数据创建并返回一个 DistributedMatrix。注意文件中存放的是一个整体的数据矩阵而不是块状循环分布后的数据,该函数会在内部完成数据分布任务。global_shape
是一个二元数组指定整体矩阵的 shape,dtype
,block_shape
he contex
同 initmpi 中对应的参数,order
指明文件中数据的排列方式,可以为 'F'(默认值) 或 'C',前者是 Fortran 的排列方式,即按照列优先的方式排列,后者是 C 的排列方式,解按照行优先的方式排列,displacement
指明从文件中开始读取数据的位置。
to_file(self, filename, order='F', displacement=0)
将该 DistributedMatrix 中的数据写入到文件 filename
中。注意写入到文件中的数据是按照整体矩阵中元素的顺序排列的,而不是按照块状循环分布后的元素顺序。order
可选 'F' 或 'C',指明写入文件中的数据矩阵的排列方式(列优先或行优先),displacement
指明在文件中的起始写入位置。
redistribute(self, block_shape=None, context=None)
将该 DistributedMatrix 按照新指定的 block_shape
和 context
重新分布,返回新创建的 DistributedMatrix。block_shape
如果为 None,会使用由 initmpi 函数所初始化的全局 _block_shape,context
如果为 None,则会使用由 initmpi 函数所初始化的全局 _context。注意:新指定的 context
必须和该 DistributedMatrix 的 ProcessContext 具有相同的 MPI 通信子。
transpose(self):
返回该 DistributedMatrix 的转置(一个新的 DistributedMatrix,其整体矩阵为原整体矩阵行和列交换后的结果)。也可以通过属性 T 获取。
conj(self)
返回该 DistributedMatrix 的复共轭矩阵(元素的实部相同,虚部反号),如果该 DistributedMatrix 的元素为实数(dtype 为 np.float32 或 np.float64),则返回的矩阵为原矩阵自身,如果其元素为复数(dtype 为 np.complex64 或 np.complex128),则会返回一个新的 DistributedMatrix。也可以通过属性 C 获取。
hconj(self)
返回该 DistributedMatrix 的厄密共轭(即转置后的复共轭),为一个新的 DistributedMatrix。也可以通过属性 H 获取。
属性
local_array
该 DistributedMatrix 分布在本进程上的子数组,为一个 numpy 数组。
desc
ScaLAPACK array descriptor,为一个整数 numpy 数组。
context
该 DistributedMatrix 的 ProcessContext 对象。
dtype
该 DistributedMatrix 数据的 numpy 类型,见前面的类型对照表。
mpi_dtype
该 DistributedMatrix 数据的 MPI 类型,见前面的类型对照表。
sc_dtype
该 DistributedMatrix 数据的 ScaLAPACK 类型,见前面的类型对照表。
global_shape
该 DistributedMatrix 的整体矩阵 shape。
local_shape
该 DistributedMatrix 分布在本进程上的子数组的 shape。
block_shape
该 DistributedMatrix 块状循环分布的块大小。
T
该 DistributedMatrix 的转置。
C
该 DistributedMatrix 的复共轭。
H
该 DistributedMatrix 的厄密共轭。
类 ScalapyException
scalapy 软件包自身产生的异常。
类 ScalapackException
调用 ScaLAPACK 过程中产生的异常。
scalapy 的计算例程 —— routines 模块
routines 模块提供了若干用于分布式内存线性代数运算的函数,这些函数在底层调用 ScaLAPACK 的相关例程完成其工作,但是其提供了与 numpy.linalg 和 scipy.linalg 一致的函数名和使用接口,因此非常方便易用。目前其提供的函数非常有限,但是常用的都已提供,下面分别对其进行介绍。
dot(A, B, transA='N', transB='N')
矩阵相乘,返回一个新的 DistributedMatrix。A
和 B
都是 DistributedMatrix,transA
和 transB
可以取值 'N','T' 或者 'C':'N' 表示用矩阵本身做运算,'T' 表示用矩阵的转置(行和列交换后的结果)做运算,'C' 表示用矩阵的厄密共轭(转置后复共轭)做运算。
inv(A, overwrite_a=True)
计算 A
的逆矩阵,A
是一个方块(行数和列数相同) DistributedMatrix,如果 overwrite_a
为 True,则 A
中的数据会在计算过程中被破坏掉,如果为 False,则运算中会使用 A
的一个复制,因此 A
中的数据会得到保持。返回由 A
的逆矩阵(也是一个 DistributedMatrix)和一个 numpy 数组组成的二元 tuple,该数组提供一些额外信息,一般不用关注。
triinv(A, lower=False, unit_triangular=False, overwrite_a=True)
计算并返回一个三角矩阵 A
的逆矩阵,返回结果为一个 DistributedMatrix。三角矩阵分为上三角矩阵和下三角矩阵,上三角矩阵是指对角线和对角线上方的元素非零,而对角线下方的元素都为零的矩阵;下三角矩阵是指对角线和对角线下方的元素非零,而对角线上方的元素都为零的矩阵。参数 A
是一个 DistributedMatrix,lower
如果为 True,会使用 A
的下三角部分做计算,其对角线以上部分会被忽略,如果为 False,则会使用 A
的上三角部分做计算,其对角线以下部分会被忽略,unit_triangular
如果为 True,表明 A
的对角元素全为 1,否则表明 A
的对角元素不全为 1,overwrite_a
如果为 True,则 A
中的数据会在计算过程中被破坏掉,如果为 False,则运算中会使用 A
的一个复制,因此 A
中的数据会得到保持。
pinv(A, overwrite_a=True)
计算并返回 A
的(Moore-Penrose)广义逆矩阵,返回结果为一个 DistributedMatrix。A
是一个 DistributedMatrix,overwrite_a
如果为 True,则 A
中的数据会在计算过程中被破坏掉,如果为 False,则运算中会使用 A
的一个复制,因此 A
中的数据会得到保持。注意:此方法要求 A
是一个满秩矩阵。
pinv2(A, overwrite_a=True, cond=None, rcond=None, return_rank=False, check_finite=True)
计算并返回 A
的(Moore-Penrose)广义逆矩阵。此方法不同于 pinv 在于它是调用(下面为介绍的) svd 函数来计算 A
的广义逆的,因此不要求 A
必须是一个满秩矩阵。A
是一个 DistributedMatrix,overwrite_a
如果为 True,则 A
中的数据会在计算过程中被破坏掉,如果为 False,则运算中会使用 A
的一个复制,因此 A
中的数据会得到保持。cond
he rcond
如果非 None 设置奇异值的截断阈值,小于 rcond*largest_singular_value 或者 cond*largest_singular_value 的奇异值会被截断为 0,否则会使用合适的机器精度对奇异值进行截断。return_rank
如果为 True,该函数会返回由 A
的广义逆矩阵和 A
的有效秩(即经过截断后非零的奇异值的个数)组成的二元 tuple,否则只返回 A
的广义逆矩阵。check_finite
指明是否首先检查 A
中所有元素都是有限数值然后再进行计算。
cholesky(A, lower=False, overwrite_a=False, zero_triangle=True)
计算对称或厄密矩阵 A
的 Cholesky 分解。参数 A
是一个 DistributedMatrix,lower
如果为 True,会计算分解 A = U*U,其中 U 是一个上三角矩阵,最后返回 U,如果为 False,则会计算分解 A = LL*,其中 L 是一个下三角矩阵,最后返回 L,overwrite_a
如果为 True,则 A
中的数据会在计算过程中被破坏掉,如果为 False,则运算中会使用 A
的一个复制,因此 A
中的数据会得到保持,zero_triangle
如果为 True,则返回的 U 或者 L 中应该为 0 的元素会被显式填充为 0,因为在计算过程中 ScaLAPACK 的相关例程会忽略对那些元素的操作,因此返回的结果那些元素可能并不为 0。
lu(A, overwrite_a=True)
计算方块矩阵 A
的 LU 分解。此方法计算的分解形式为 A = P L U,其中 P 是一个置换矩阵,L 是一个对角元素都为 1 的下三角矩阵,U是一个上三角矩阵。A
是一个 DistributedMatrix,overwrite_a
如果为 True,则 A
中的数据会在计算过程中被破坏掉,如果为 False,则运算中会使用 A
的一个复制,因此 A
中的数据会得到保持。返回由一个 DistributedMatrix 和一个 numpy 数组组成的二元 tuple,分解因子 L 和 U 分布存储在该 DistributedMatrix 的下三角和上三角部分,但是 L 的对角元素(全为 1)没有存储,在返回的 numpy 数组中包含转置的相关信息。
eigh(A, B=None, lower=True, eigvals_only=False, overwrite_a=True, overwrite_b=True, type_=1, eigbounds=None, eigvals=None)
求解对称或厄密矩阵的本征值(或广义本征值)问题。A
为一个实对称或者复厄密 DistributedMatrix,B
如果非 None,应该是一个实对称或者复厄密的正定 DistributedMatrix,否则会使用单位矩阵,lower
如果为 True,会使用 A
的下三角部分做计算,其对角线以上部分会被忽略,如果为 False,则会使用 A
的上三角部分做计算,其对角线以下部分会被忽略,eigvals_only
如果为 False,会计算和返回本征值(一个 numpy 数组,每个进程都会是同样都结果,包含所有本征值)及本征向量矩阵(一个 DistributedMatrix),如果为 True,则只会计算和返回本征值数组,overwrite_a
(overwrite_b
)指明是否允许在计算过程中破坏原矩阵 A
(B
),如果为 False,会使用原矩阵的一个复制,因此会使用更多的内存,type_
指明计算类型(见下面的说明),eigbounds
如果非 None,应该是一个二元 tuple (vl, vu),指定只计算处于下限 vl 和上限 vu 之间的本征值和本征向量,eigvals
如果非 None,应该是一个二元 tuple (lo, hi),指定只计算第 lo 到第 hi (包含第 hi)个本征值及本征向量。
type_
可取值 1, 2 或 3,分别对应求解下面的问题:
- type_ = 1: A v[:,i] = w[i] B v[:,i]
- type_ = 2: A B v[:,i] = w[i] v[:,i]
- type_ = 3: B A v[:,i] = w[i] v[:,i]
svd(A, overwrite_a=True, compute_u=True, compute_v=True)
计算 A
的奇异值分解。分解形式为 A = U * np.diag(s) * V*,U 和 V 都是幺正(或实正交)矩阵,s 是一个 1d numpy 数组包含从大到小排列的奇异值。A
是一个 DistributedMatrix,overwrite_a
如果为 True,则 A
中的数据会在计算过程中被破坏掉,如果为 False,则运算中会使用 A
的一个复制,因此 A
中的数据会得到保持,compute_u
(compute_v
)指明是否返回 U (V*),如果都返回,结果为一个三元 tuple (U, s, V*),其中 U 和 V* 都是 DistributedMatrix,s 是一个 1d numpy 数组(所有进程都会返回同样的结果),如果不返回 U 或者 V*,结果为一个二元 tuple (U, s) 或者 (s, V*),如果 U 和 V* 都不返回,则结果为 s。
transpose(A)
计算并返回 A
的转置。A
是一个 DistributedMatrix。
conj(A)
计算并返回 A
的复共轭。A
是一个 DistributedMatrix。
hconj(A)
计算并返回 A
的厄密共轭。A
是一个 DistributedMatrix。
使用步骤
使用 scalapy 的步骤和上一篇中介绍的使用 ScaLAPACK 的步骤基本一致:
初始化进程网格;
可以调用 initmpi 函数初始化一个全局的 ProcessContext 对象或者由 ProcessContext 的构造函数手动初始化一个 ProcessContext 对象。将数据创建为一个 DistributedMatrix,这会将数据(矩阵或向量)按照块状循环方式分布到进程网格上;
可以采用多种方式将数据创建为 DistributedMatrix,如可以调用 DistributedMatrix.from_global_array 由一个整体的 numpy 数组创建,可以调用 DistributedMatrix.from_file 从文件中读取数据创建,或者手动创建(对于一些比较特殊的容易创建的 DistributedMatrix)。调用 DistributedMatrix 自身的方法或 scalapy.routines 模块的求解例程以完成计算。
计算的结果一般都是 DistributedMatrix,可以通过调用 DistributedMatrix.to_global_array 将其转化为一个整体的 numpy 数组,或调用 DistributedMatrix.to_file 将其对应的整体矩阵存入到文件。
例程
下面给出简单的使用例程。
# scalapy_demo.py
"""
Demonstrate the use of scalapy.
Run this with 4 process like:
$ mpiexec -n 4 python scalapy_demo.py
"""
import os
import numpy as np
import scipy.linalg as la
from mpi4py import MPI
from scalapy import core
import scalapy.routines as rt
comm = MPI.COMM_WORLD
rank = comm.rank
size = comm.size
if size != 4:
raise Exception("Must run with 4 processes")
# define a function to compare whether two arrays are equal
allclose = lambda a, b: np.allclose(a, b, rtol=1e-4, atol=1e-6)
# initialize a global ProcessContext object,
# which includes the initialization of a 2 x 2 process grid
core.initmpi([2, 2], block_shape=[16, 16])
N = 300
# create a N x N numpy array with random numbers
gA = np.random.standard_normal((N, N)).astype(np.float64)
gA = np.asfortranarray(gA)
# create a DistributedMatrix from gA
dA = core.DistributedMatrix.from_global_array(gA, rank=0)
print 'rank %d has global_shape of dA = %s' % (rank, dA.global_shape)
print 'rank %d has local_shape of dA = %s' % (rank, dA.local_shape)
print 'rank %d has block_shape of dA = %s' % (rank, dA.block_shape)
# compute the inverse of dA
invA, ipiv = rt.inv(dA)
# convert to a global numpy array hold by rank 0 only
ginvA = invA.to_global_array(rank=0)
if rank == 0:
# compare the result with that of scipy.linalg.inv
print 'result equals that of scipy: ', allclose(ginvA, la.inv(gA))
# write dA to file
file_name = 'dA.dat'
dA.to_file(file_name)
# now read it from file and check it equals the original DistributedMatrix
dA1 = core.DistributedMatrix.from_file(file_name, dA.global_shape, dA.dtype, dA.block_shape, dA.context)
print 'rank %d has dA.local_array == dA1.local_array: %s' % (rank, allclose(dA.local_array, dA1.local_array))
# remove the file
if rank == 0:
os.remove(file_name)
运行结果如下:
$ mpiexec -n 4 python scalapy_demo.py
rank 0 has global_shape of dA = (300, 300)
rank 0 has local_shape of dA = (156, 156)
rank 0 has block_shape of dA = (16, 16)
rank 1 has global_shape of dA = (300, 300)
rank 1 has local_shape of dA = (156, 144)
rank 1 has block_shape of dA = (16, 16)
rank 2 has global_shape of dA = (300, 300)
rank 2 has local_shape of dA = (144, 156)
rank 2 has block_shape of dA = (16, 16)
rank 3 has global_shape of dA = (300, 300)
rank 3 has local_shape of dA = (144, 144)
rank 3 has block_shape of dA = (16, 16)
result equals that of scipy: True
rank 3 has dA.local_array == dA1.local_array: True
rank 1 has dA.local_array == dA1.local_array: True
rank 2 has dA.local_array == dA1.local_array: True
rank 0 has dA.local_array == dA1.local_array: True
以上介绍了使用 scalapy 调用 ScaLAPACK 进行分布式内存的线性代数运算。Python 作为一种胶水语言,可以非常容易地包装和调用其它计算机语言已有的程序代码和工具库,如果我们有用 C,C++,Fortran 或其它计算机语言编写的 MPI 计算程序,也能很容易地将其包装后在 mpi4py 中进行调用。另外我们也可以用这些计算机语言编写一些运算速度更快的 MPI 扩展模块供 mpi4py 程序调用。在下面的章节中我们将介绍怎么在 mpi4py 程序中包装和调用 C,C++,Fortran 的 MPI 程序。在下一篇中我们首先介绍直接使用 Python C API 进行 C 语言 MPI 程序包装的方法。