使用Cython包装LAPACKE函数

6

我正在尝试使用Cython包装LAPACK函数dgtsv(用于三对角线性方程组的求解器)。

我找到了这个之前的答案,但由于dgtsv不是在scipy.linalg中包装的LAPACK函数之一,所以我不认为我可以使用这种特定的方法。相反,我一直在尝试遵循这个例子

这是我的lapacke.pxd文件的内容:

ctypedef int lapack_int

cdef extern from "lapacke.h" nogil:

    int LAPACK_ROW_MAJOR
    int LAPACK_COL_MAJOR

    lapack_int LAPACKE_dgtsv(int matrix_order,
                             lapack_int n,
                             lapack_int nrhs,
                             double * dl,
                             double * d,
                             double * du,
                             double * b,
                             lapack_int ldb)

这是我的Cython薄包装器_solvers.pyx

#!python

cimport cython
from lapacke cimport *

cpdef TDMA_lapacke(double[::1] DL, double[::1] D, double[::1] DU,
                   double[:, ::1] B):

    cdef:
        lapack_int n = D.shape[0]
        lapack_int nrhs = B.shape[1]
        lapack_int ldb = B.shape[0]
        double * dl = &DL[0]
        double * d = &D[0]
        double * du = &DU[0]
        double * b = &B[0, 0]
        lapack_int info

    info = LAPACKE_dgtsv(LAPACK_ROW_MAJOR, n, nrhs, dl, d, du, b, ldb)

    return info

...这里是Python的包装器和测试脚本:

import numpy as np
from scipy import sparse
from cymodules import _solvers


def trisolve_lapacke(dl, d, du, b, inplace=False):

    if (dl.shape[0] != du.shape[0] or dl.shape[0] != d.shape[0] - 1
            or b.shape != d.shape):
        raise ValueError('Invalid diagonal shapes')

    if b.ndim == 1:
        # b is (LDB, NRHS)
        b = b[:, None]

    # be sure to force a copy of d and b if we're not solving in place
    if not inplace:
        d = d.copy()
        b = b.copy()

    # this may also force copies if arrays are improperly typed/noncontiguous
    dl, d, du, b = (np.ascontiguousarray(v, dtype=np.float64)
                    for v in (dl, d, du, b))

    # b will now be modified in place to contain the solution
    info = _solvers.TDMA_lapacke(dl, d, du, b)
    print info

    return b.ravel()


def test_trisolve(n=20000):

    dl = np.random.randn(n - 1)
    d = np.random.randn(n)
    du = np.random.randn(n - 1)

    M = sparse.diags((dl, d, du), (-1, 0, 1), format='csc')
    x = np.random.randn(n)
    b = M.dot(x)

    x_hat = trisolve_lapacke(dl, d, du, b)

    print "||x - x_hat|| = ", np.linalg.norm(x - x_hat)

很遗憾,test_trisolve 在调用 _solvers.TDMA_lapacke 时出现了段错误。我非常确定我的 setup.py 是正确的 - ldd _solvers.so 显示在运行时 _solvers.so 正确地链接到了共享库。

我不太确定接下来该怎么做 - 有什么建议吗?


简要更新:

对于较小的n值,我通常不会立即遇到段错误,但我会得到无意义的结果(||x-x_hat||应该非常接近0):

In [28]: test_trisolve2.test_trisolve(10)
0
||x - x_hat|| =  6.23202576396

In [29]: test_trisolve2.test_trisolve(10)
-7
||x - x_hat|| =  3.88623414288

In [30]: test_trisolve2.test_trisolve(10)
0
||x - x_hat|| =  2.60190676562

In [31]: test_trisolve2.test_trisolve(10)
0
||x - x_hat|| =  3.86631743386

In [32]: test_trisolve2.test_trisolve(10)
Segmentation fault

通常情况下,LAPACKE_dgtsv 返回代码 0(应表示成功),但有时会返回 -7,这意味着第 7 个参数(b)存在非法值。问题在于只有第一个 b 的值实际上被就地修改。如果我继续调用 test_trisolve,即使 n 很小,最终也会遇到段错误。
2个回答

4

好的,我最终弄清楚了——看来我在这种情况下误解了行主序和列主序的含义。

由于C连续数组遵循行主序,我认为应该将LAPACK_ROW_MAJOR指定为LAPACKE_dgtsv的第一个参数。

实际上,如果我更改

info = LAPACKE_dgtsv(LAPACK_ROW_MAJOR, ...)

to

info = LAPACKE_dgtsv(LAPACK_COL_MAJOR, ...)

然后我的函数就能够工作:

test_trisolve2.test_trisolve()
0
||x - x_hat|| =  6.67064747632e-12

这对我来说似乎相当违反直觉 - 有人能解释一下为什么会这样吗?

1
尽管问题比较老,但似乎仍然相关。观察到的行为是参数LDB被误解所致:
- Fortran数组是列优先的,数组B的主维度对应于N。因此,LDB >= max(1,N)。 - 对于行优先,LDB对应于NRHS,因此必须满足条件LDB >= max(1,NRHS)。
注释“# b is (LDB, NRHS)”不正确,因为b的维度为(LDB,N),在这种情况下LDB应该为1。
将从LAPACK_ROW_MAJOR切换到LAPACK_COL_MAJOR可以解决问题,只要NRHS等于1即可。列优先的(N,1)的内存布局与行优先的(1,N)相同。但是,如果NRHS大于1,则会失败。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接