在Python中从对角矢量创建密集矩阵的有效方法?

时间:2015-08-05 14:07:31

标签: python numpy

我正在尝试使用numpy向量在Python中创建此矩阵:

matrix

其中值来自函数。我已经使用numpy.diag重复实现它,但对于大尺寸,它变得非常慢。这是代码:

def makeS(N):
    vec = np.full(N, 2*v(x_range[1]))
    vec[0]*=0.5 
    S = np.diag(vec)

    vec = np.full(N-1, v(x_range[0]))   
    S+= np.diag(vec, 1)

    for m in xrange(1, N):
        vec =  np.full(N-m, 2*v(x_range[m+1]))
        vec[0]*= 0.5
        S += np.diag(vec, -m)
    return S

其中v()是所述函数,x_range是x值的向量。有没有办法提高效率?

编辑: 这是一个完整的例子:

import numpy as np
import math 

N = 5
x_range = np.linspace(0, 1, N+1)


def v(x):
    return math.exp(x)

def makeS(N):
    vec = np.full(N, 2*v(x_range[1]))
    vec[0]*=0.5 
    S = np.diag(vec)

    vec = np.full(N-1, v(x_range[0]))   
    S+= np.diag(vec, 1)

    for m in xrange(1, N):
        vec =  np.full(N-m, 2*v(x_range[m+1]))
        vec[0]*= 0.5
        S += np.diag(vec, -m)
    return S


print makeS(N)

输出

[[ 1.22140276  1.          0.          0.          0.        ]
 [ 1.4918247   2.44280552  1.          0.          0.        ]
 [ 1.8221188   2.9836494   2.44280552  1.          0.        ]
 [ 2.22554093  3.6442376   2.9836494   2.44280552  1.        ]
 [ 2.71828183  4.45108186  3.6442376   2.9836494   2.44280552]]

1 个答案:

答案 0 :(得分:1)

这是我能找到的最快的方法:

def makeS(N):
    values = np.array([v(x) for x in x_range])
    values_doubled = 2 * values
    result = np.eye(N, k=1) * values[0]
    result[:, 0] = values[1:]
    for i in xrange(N - 1):
        result[i + 1, 1:i + 2] = values_doubled[1:i + 2][::-1]
    return result

使用N=2000原始设备在我的机器上需要26.97秒,而新版本需要0.02339秒。

以下是使用其他方法评估时间的完整脚本。

import numpy as np
import math
import timeit


def v(x):
    return math.exp(x)


def makeS1(N, x_range):
    vec = np.full(N, 2 * v(x_range[1]))
    vec[0] *= 0.5
    S = np.diag(vec)
    vec = np.full(N - 1, v(x_range[0]))
    S += np.diag(vec, 1)
    for m in xrange(1, N):
        vec = np.full(N - m, 2 * v(x_range[m + 1]))
        vec[0] *= 0.5
        S += np.diag(vec, -m)
    return S


def makeS2(N, x_range):
    values = np.array([v(x) for x in x_range])
    values_doubled = 2 * values

    def value_at_position(ai, aj):
        result = np.zeros((N, N))
        for i, j in zip(ai.flatten(), aj.flatten()):
            if j > i + 1:
                continue
            elif j == i + 1:
                result[i, j] = values[0]
            elif j == 0:
                result[i, j] = values[i + 1]
            else:
                result[i, j] = values_doubled[i - j + 1]
        return result

    return np.fromfunction(value_at_position, (N, N))


def makeS3(N, x_range):
    values = np.array([v(x) for x in x_range])
    values_doubled = 2 * values
    result = np.zeros((N, N))
    for i in xrange(N):
        for j in xrange(min(i + 2, N)):
            if j == i + 1:
                result[i, j] = values[0]
            elif j == 0:
                result[i, j] = values[i + 1]
            else:
                result[i, j] = values_doubled[i - j + 1]
    return result


def makeS4(N, x_range):
    values = np.array([v(x) for x in x_range])
    values_doubled = 2 * values
    result = np.eye(N, k=1) * values[0]
    result[:, 0] = values[1:]
    for i in xrange(N - 1):
        result[i + 1, 1:i + 2] = values_doubled[1:i + 2][::-1]
    return result


def main():
    N = 2000
    x_range = np.random.randn(N + 1)

    start = timeit.default_timer()
    s1 = makeS1(N, x_range)
    print 'makeS1', timeit.default_timer() - start
    start = timeit.default_timer()
    s2 = makeS2(N, x_range)
    print 'makeS2', timeit.default_timer() - start
    start = timeit.default_timer()
    s3 = makeS3(N, x_range)
    print 'makeS3', timeit.default_timer() - start
    start = timeit.default_timer()
    s4 = makeS4(N, x_range)
    print 'makeS4', timeit.default_timer() - start
    if N < 10:
        print s1
        print s2
        print s2
        print s4
    assert np.allclose(s1, s2)
    assert np.allclose(s2, s3)
    assert np.allclose(s3, s4)


main()

在我的机器上,这会产生输出:

makeS1 26.9707232448
makeS2 11.7728229076
makeS3 0.643742975052
makeS4 0.0233912765665