目次

8. 連立一次方程式

8.1 NumPy,SciPy,CuPyのソースコード

リスト8-1にNumPy,SciPy,CuPyを用いて連立一次方程式を解くソースコードを示します。
NumPyとSciPyはCPU用、CuPyはGPU用です。 いずれもlinalg.solve関数を使用しています。
実数と複素数、単精度と倍精度を選択することができます。
行列は乱数で作成した非対称密行列です。
CuPyではデバイスメモリーを確実に解放するためにmemory poolを使用しています。

リスト8-1 NumP,SciPy,CuPyを用いて連立一次方程式を解くソースコード (leq.py)


"""
leq.py
solve linear equations
NumPy, SciPy, CuPy
"""

import numpy as np
import scipy as sp
import cupy as cp
import time

# parameters
N = 5000  # matrix size
L = 1  # repeat
dkind = 1      # 1=real, 2=complex
solver = 1     # 1=NumPy, 2=SciPy, 3=CuPy
precision = 1  # 1=single, 2=double

# dtype
f_dtype = 'f4' if precision == 1 else 'f8'
c_dtype = 'c8' if precision == 1 else 'c16'

# timer
t0 = time.time()

# real
if dkind == 1:
    a = np.random.rand(N, N).astype(f_dtype)
    b = np.random.rand(N).astype(f_dtype)
    x = np.zeros(N, f_dtype)
# complex
elif dkind == 2:
    a = np.random.rand(N, N).astype(f_dtype) \
      + np.random.rand(N, N).astype(f_dtype) * 1j
    b = np.random.rand(N).astype(f_dtype) \
      + np.random.rand(N).astype(f_dtype) * 1j
    x = np.zeros(N, c_dtype)

# well-condition
for i in range(N):
    a[i, i] *= 10

# alloc device memory
if solver == 3:
    mempool = cp.get_default_memory_pool()
    # copy to device
    d_a = cp.array(a)
    d_b = cp.array(b)
    d_x = cp.array(x)

# timer
t1 = time.time()

# solve
for _ in range(L):
    if   solver == 1:
        # NumPy (CPU)
        x = np.linalg.solve(a, b)
    elif solver == 2:
        # SciPy (CPU)
        x = sp.linalg.solve(a, b)
    elif solver == 3:
        # CuPy (GPU)
        d_x = cp.linalg.solve(d_a, d_b)

# timer
t2 = time.time()

# free device memory
if solver == 3:
    # copy to host
    x = cp.asnumpy(d_x)
    # free
    d_a = None
    d_b = None
    d_x = None
    mempool.free_all_blocks()

# check
res = np.dot(a, x) - b
ans = np.linalg.norm(res)

# output
device = 'CPU(NumPy)' if solver == 1 else 'CPU(SciPy)' if solver == 2 else 'GPU(CuPy)'
print('%s N=%d(%d), %.2f+%.2f[sec], err=%.3e'
    % (device, N, L, t1 - t0, (t2 - t1) / L, ans))

# free
a = None
b = None
x = None

8.2 実数の計算時間

表8-1に実数の連立一次方程式の計算時間を示します。
計算時間を正確に測定するために、 計算時間が短いときは多数回計算してその回数で割っています。

表8-1 連立一次方程式の計算時間(実数、非対称密行列)
行列の大きさNCPU (NumPy)CPU (SciPy)GPU (CuPy)
単精度倍精度単精度倍精度単精度倍精度
5000 0.42秒 0.38秒 0.31秒 0.51秒0.03秒 0.45秒
10000 2.29秒 2.16秒 1.41秒 2.56秒0.16秒 3.39秒
2000016.56秒14.79秒11.69秒22.17秒0.95秒26.83秒

表8-1から以下のことがわかります。

以上から、連立一次方程式を解くときの指針は以下のようになります。

8.3 複素数の計算時間

表8-2に複素数の連立一次方程式の計算時間を示します。
表8-1と同じ傾向が見られます。 複素数の計算時間は実数の3~5倍になります。

表8-2 連立一次方程式の計算時間(複素数、非対称密行列)
行列の大きさNCPU (NumPy)CPU (SciPy)GPU (CuPy)
単精度倍精度単精度倍精度単精度倍精度
5000 1.03秒 0.96秒 0.79秒 1.56秒0.07秒 1.52秒
10000 8.75秒 6.75秒 4.38秒 9.10秒0.39秒12.25秒
2000090.45秒127.64秒34.36秒115.87秒2.95秒メモリー不足