608 lines
18 KiB
Python
608 lines
18 KiB
Python
# Test interfaces to fortran blas.
|
|
#
|
|
# The tests are more of interface than they are of the underlying blas.
|
|
# Only very small matrices checked -- N=3 or so.
|
|
#
|
|
# !! Complex calculations really aren't checked that carefully.
|
|
# !! Only real valued complex numbers are used in tests.
|
|
|
|
from numpy import float32, float64, complex64, complex128, arange, array, \
|
|
zeros, shape, transpose, newaxis, common_type, conjugate
|
|
|
|
from scipy.linalg import _fblas as fblas
|
|
|
|
from numpy.testing import assert_array_equal, \
|
|
assert_allclose, assert_array_almost_equal, assert_
|
|
|
|
import pytest
|
|
|
|
# decimal accuracy to require between Python and LAPACK/BLAS calculations
|
|
accuracy = 5
|
|
|
|
# Since numpy.dot likely uses the same blas, use this routine
|
|
# to check.
|
|
|
|
|
|
def matrixmultiply(a, b):
|
|
if len(b.shape) == 1:
|
|
b_is_vector = True
|
|
b = b[:, newaxis]
|
|
else:
|
|
b_is_vector = False
|
|
assert_(a.shape[1] == b.shape[0])
|
|
c = zeros((a.shape[0], b.shape[1]), common_type(a, b))
|
|
for i in range(a.shape[0]):
|
|
for j in range(b.shape[1]):
|
|
s = 0
|
|
for k in range(a.shape[1]):
|
|
s += a[i, k] * b[k, j]
|
|
c[i, j] = s
|
|
if b_is_vector:
|
|
c = c.reshape((a.shape[0],))
|
|
return c
|
|
|
|
##################################################
|
|
# Test blas ?axpy
|
|
|
|
|
|
class BaseAxpy:
|
|
''' Mixin class for axpy tests '''
|
|
|
|
def test_default_a(self):
|
|
x = arange(3., dtype=self.dtype)
|
|
y = arange(3., dtype=x.dtype)
|
|
real_y = x*1.+y
|
|
y = self.blas_func(x, y)
|
|
assert_array_equal(real_y, y)
|
|
|
|
def test_simple(self):
|
|
x = arange(3., dtype=self.dtype)
|
|
y = arange(3., dtype=x.dtype)
|
|
real_y = x*3.+y
|
|
y = self.blas_func(x, y, a=3.)
|
|
assert_array_equal(real_y, y)
|
|
|
|
def test_x_stride(self):
|
|
x = arange(6., dtype=self.dtype)
|
|
y = zeros(3, x.dtype)
|
|
y = arange(3., dtype=x.dtype)
|
|
real_y = x[::2]*3.+y
|
|
y = self.blas_func(x, y, a=3., n=3, incx=2)
|
|
assert_array_equal(real_y, y)
|
|
|
|
def test_y_stride(self):
|
|
x = arange(3., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
real_y = x*3.+y[::2]
|
|
y = self.blas_func(x, y, a=3., n=3, incy=2)
|
|
assert_array_equal(real_y, y[::2])
|
|
|
|
def test_x_and_y_stride(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
real_y = x[::4]*3.+y[::2]
|
|
y = self.blas_func(x, y, a=3., n=3, incx=4, incy=2)
|
|
assert_array_equal(real_y, y[::2])
|
|
|
|
def test_x_bad_size(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
with pytest.raises(Exception, match='failed for 1st keyword'):
|
|
self.blas_func(x, y, n=4, incx=5)
|
|
|
|
def test_y_bad_size(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
with pytest.raises(Exception, match='failed for 1st keyword'):
|
|
self.blas_func(x, y, n=3, incy=5)
|
|
|
|
|
|
try:
|
|
class TestSaxpy(BaseAxpy):
|
|
blas_func = fblas.saxpy
|
|
dtype = float32
|
|
except AttributeError:
|
|
class TestSaxpy:
|
|
pass
|
|
|
|
|
|
class TestDaxpy(BaseAxpy):
|
|
blas_func = fblas.daxpy
|
|
dtype = float64
|
|
|
|
|
|
try:
|
|
class TestCaxpy(BaseAxpy):
|
|
blas_func = fblas.caxpy
|
|
dtype = complex64
|
|
except AttributeError:
|
|
class TestCaxpy:
|
|
pass
|
|
|
|
|
|
class TestZaxpy(BaseAxpy):
|
|
blas_func = fblas.zaxpy
|
|
dtype = complex128
|
|
|
|
|
|
##################################################
|
|
# Test blas ?scal
|
|
|
|
class BaseScal:
|
|
''' Mixin class for scal testing '''
|
|
|
|
def test_simple(self):
|
|
x = arange(3., dtype=self.dtype)
|
|
real_x = x*3.
|
|
x = self.blas_func(3., x)
|
|
assert_array_equal(real_x, x)
|
|
|
|
def test_x_stride(self):
|
|
x = arange(6., dtype=self.dtype)
|
|
real_x = x.copy()
|
|
real_x[::2] = x[::2]*array(3., self.dtype)
|
|
x = self.blas_func(3., x, n=3, incx=2)
|
|
assert_array_equal(real_x, x)
|
|
|
|
def test_x_bad_size(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
with pytest.raises(Exception, match='failed for 1st keyword'):
|
|
self.blas_func(2., x, n=4, incx=5)
|
|
|
|
|
|
try:
|
|
class TestSscal(BaseScal):
|
|
blas_func = fblas.sscal
|
|
dtype = float32
|
|
except AttributeError:
|
|
class TestSscal:
|
|
pass
|
|
|
|
|
|
class TestDscal(BaseScal):
|
|
blas_func = fblas.dscal
|
|
dtype = float64
|
|
|
|
|
|
try:
|
|
class TestCscal(BaseScal):
|
|
blas_func = fblas.cscal
|
|
dtype = complex64
|
|
except AttributeError:
|
|
class TestCscal:
|
|
pass
|
|
|
|
|
|
class TestZscal(BaseScal):
|
|
blas_func = fblas.zscal
|
|
dtype = complex128
|
|
|
|
|
|
##################################################
|
|
# Test blas ?copy
|
|
|
|
class BaseCopy:
|
|
''' Mixin class for copy testing '''
|
|
|
|
def test_simple(self):
|
|
x = arange(3., dtype=self.dtype)
|
|
y = zeros(shape(x), x.dtype)
|
|
y = self.blas_func(x, y)
|
|
assert_array_equal(x, y)
|
|
|
|
def test_x_stride(self):
|
|
x = arange(6., dtype=self.dtype)
|
|
y = zeros(3, x.dtype)
|
|
y = self.blas_func(x, y, n=3, incx=2)
|
|
assert_array_equal(x[::2], y)
|
|
|
|
def test_y_stride(self):
|
|
x = arange(3., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
y = self.blas_func(x, y, n=3, incy=2)
|
|
assert_array_equal(x, y[::2])
|
|
|
|
def test_x_and_y_stride(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
y = self.blas_func(x, y, n=3, incx=4, incy=2)
|
|
assert_array_equal(x[::4], y[::2])
|
|
|
|
def test_x_bad_size(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
with pytest.raises(Exception, match='failed for 1st keyword'):
|
|
self.blas_func(x, y, n=4, incx=5)
|
|
|
|
def test_y_bad_size(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
with pytest.raises(Exception, match='failed for 1st keyword'):
|
|
self.blas_func(x, y, n=3, incy=5)
|
|
|
|
# def test_y_bad_type(self):
|
|
## Hmmm. Should this work? What should be the output.
|
|
# x = arange(3.,dtype=self.dtype)
|
|
# y = zeros(shape(x))
|
|
# self.blas_func(x,y)
|
|
# assert_array_equal(x,y)
|
|
|
|
|
|
try:
|
|
class TestScopy(BaseCopy):
|
|
blas_func = fblas.scopy
|
|
dtype = float32
|
|
except AttributeError:
|
|
class TestScopy:
|
|
pass
|
|
|
|
|
|
class TestDcopy(BaseCopy):
|
|
blas_func = fblas.dcopy
|
|
dtype = float64
|
|
|
|
|
|
try:
|
|
class TestCcopy(BaseCopy):
|
|
blas_func = fblas.ccopy
|
|
dtype = complex64
|
|
except AttributeError:
|
|
class TestCcopy:
|
|
pass
|
|
|
|
|
|
class TestZcopy(BaseCopy):
|
|
blas_func = fblas.zcopy
|
|
dtype = complex128
|
|
|
|
|
|
##################################################
|
|
# Test blas ?swap
|
|
|
|
class BaseSwap:
|
|
''' Mixin class for swap tests '''
|
|
|
|
def test_simple(self):
|
|
x = arange(3., dtype=self.dtype)
|
|
y = zeros(shape(x), x.dtype)
|
|
desired_x = y.copy()
|
|
desired_y = x.copy()
|
|
x, y = self.blas_func(x, y)
|
|
assert_array_equal(desired_x, x)
|
|
assert_array_equal(desired_y, y)
|
|
|
|
def test_x_stride(self):
|
|
x = arange(6., dtype=self.dtype)
|
|
y = zeros(3, x.dtype)
|
|
desired_x = y.copy()
|
|
desired_y = x.copy()[::2]
|
|
x, y = self.blas_func(x, y, n=3, incx=2)
|
|
assert_array_equal(desired_x, x[::2])
|
|
assert_array_equal(desired_y, y)
|
|
|
|
def test_y_stride(self):
|
|
x = arange(3., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
desired_x = y.copy()[::2]
|
|
desired_y = x.copy()
|
|
x, y = self.blas_func(x, y, n=3, incy=2)
|
|
assert_array_equal(desired_x, x)
|
|
assert_array_equal(desired_y, y[::2])
|
|
|
|
def test_x_and_y_stride(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
desired_x = y.copy()[::2]
|
|
desired_y = x.copy()[::4]
|
|
x, y = self.blas_func(x, y, n=3, incx=4, incy=2)
|
|
assert_array_equal(desired_x, x[::4])
|
|
assert_array_equal(desired_y, y[::2])
|
|
|
|
def test_x_bad_size(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
with pytest.raises(Exception, match='failed for 1st keyword'):
|
|
self.blas_func(x, y, n=4, incx=5)
|
|
|
|
def test_y_bad_size(self):
|
|
x = arange(12., dtype=self.dtype)
|
|
y = zeros(6, x.dtype)
|
|
with pytest.raises(Exception, match='failed for 1st keyword'):
|
|
self.blas_func(x, y, n=3, incy=5)
|
|
|
|
|
|
try:
|
|
class TestSswap(BaseSwap):
|
|
blas_func = fblas.sswap
|
|
dtype = float32
|
|
except AttributeError:
|
|
class TestSswap:
|
|
pass
|
|
|
|
|
|
class TestDswap(BaseSwap):
|
|
blas_func = fblas.dswap
|
|
dtype = float64
|
|
|
|
|
|
try:
|
|
class TestCswap(BaseSwap):
|
|
blas_func = fblas.cswap
|
|
dtype = complex64
|
|
except AttributeError:
|
|
class TestCswap:
|
|
pass
|
|
|
|
|
|
class TestZswap(BaseSwap):
|
|
blas_func = fblas.zswap
|
|
dtype = complex128
|
|
|
|
##################################################
|
|
# Test blas ?gemv
|
|
# This will be a mess to test all cases.
|
|
|
|
|
|
class BaseGemv:
|
|
''' Mixin class for gemv tests '''
|
|
|
|
def get_data(self, x_stride=1, y_stride=1):
|
|
mult = array(1, dtype=self.dtype)
|
|
if self.dtype in [complex64, complex128]:
|
|
mult = array(1+1j, dtype=self.dtype)
|
|
from numpy.random import normal, seed
|
|
seed(1234)
|
|
alpha = array(1., dtype=self.dtype) * mult
|
|
beta = array(1., dtype=self.dtype) * mult
|
|
a = normal(0., 1., (3, 3)).astype(self.dtype) * mult
|
|
x = arange(shape(a)[0]*x_stride, dtype=self.dtype) * mult
|
|
y = arange(shape(a)[1]*y_stride, dtype=self.dtype) * mult
|
|
return alpha, beta, a, x, y
|
|
|
|
def test_simple(self):
|
|
alpha, beta, a, x, y = self.get_data()
|
|
desired_y = alpha*matrixmultiply(a, x)+beta*y
|
|
y = self.blas_func(alpha, a, x, beta, y)
|
|
assert_array_almost_equal(desired_y, y)
|
|
|
|
def test_default_beta_y(self):
|
|
alpha, beta, a, x, y = self.get_data()
|
|
desired_y = matrixmultiply(a, x)
|
|
y = self.blas_func(1, a, x)
|
|
assert_array_almost_equal(desired_y, y)
|
|
|
|
def test_simple_transpose(self):
|
|
alpha, beta, a, x, y = self.get_data()
|
|
desired_y = alpha*matrixmultiply(transpose(a), x)+beta*y
|
|
y = self.blas_func(alpha, a, x, beta, y, trans=1)
|
|
assert_array_almost_equal(desired_y, y)
|
|
|
|
def test_simple_transpose_conj(self):
|
|
alpha, beta, a, x, y = self.get_data()
|
|
desired_y = alpha*matrixmultiply(transpose(conjugate(a)), x)+beta*y
|
|
y = self.blas_func(alpha, a, x, beta, y, trans=2)
|
|
assert_array_almost_equal(desired_y, y)
|
|
|
|
def test_x_stride(self):
|
|
alpha, beta, a, x, y = self.get_data(x_stride=2)
|
|
desired_y = alpha*matrixmultiply(a, x[::2])+beta*y
|
|
y = self.blas_func(alpha, a, x, beta, y, incx=2)
|
|
assert_array_almost_equal(desired_y, y)
|
|
|
|
def test_x_stride_transpose(self):
|
|
alpha, beta, a, x, y = self.get_data(x_stride=2)
|
|
desired_y = alpha*matrixmultiply(transpose(a), x[::2])+beta*y
|
|
y = self.blas_func(alpha, a, x, beta, y, trans=1, incx=2)
|
|
assert_array_almost_equal(desired_y, y)
|
|
|
|
def test_x_stride_assert(self):
|
|
# What is the use of this test?
|
|
alpha, beta, a, x, y = self.get_data(x_stride=2)
|
|
with pytest.raises(Exception, match='failed for 3rd argument'):
|
|
y = self.blas_func(1, a, x, 1, y, trans=0, incx=3)
|
|
with pytest.raises(Exception, match='failed for 3rd argument'):
|
|
y = self.blas_func(1, a, x, 1, y, trans=1, incx=3)
|
|
|
|
def test_y_stride(self):
|
|
alpha, beta, a, x, y = self.get_data(y_stride=2)
|
|
desired_y = y.copy()
|
|
desired_y[::2] = alpha*matrixmultiply(a, x)+beta*y[::2]
|
|
y = self.blas_func(alpha, a, x, beta, y, incy=2)
|
|
assert_array_almost_equal(desired_y, y)
|
|
|
|
def test_y_stride_transpose(self):
|
|
alpha, beta, a, x, y = self.get_data(y_stride=2)
|
|
desired_y = y.copy()
|
|
desired_y[::2] = alpha*matrixmultiply(transpose(a), x)+beta*y[::2]
|
|
y = self.blas_func(alpha, a, x, beta, y, trans=1, incy=2)
|
|
assert_array_almost_equal(desired_y, y)
|
|
|
|
def test_y_stride_assert(self):
|
|
# What is the use of this test?
|
|
alpha, beta, a, x, y = self.get_data(y_stride=2)
|
|
with pytest.raises(Exception, match='failed for 2nd keyword'):
|
|
y = self.blas_func(1, a, x, 1, y, trans=0, incy=3)
|
|
with pytest.raises(Exception, match='failed for 2nd keyword'):
|
|
y = self.blas_func(1, a, x, 1, y, trans=1, incy=3)
|
|
|
|
|
|
try:
|
|
class TestSgemv(BaseGemv):
|
|
blas_func = fblas.sgemv
|
|
dtype = float32
|
|
|
|
def test_sgemv_on_osx(self):
|
|
from itertools import product
|
|
import sys
|
|
import numpy as np
|
|
|
|
if sys.platform != 'darwin':
|
|
return
|
|
|
|
def aligned_array(shape, align, dtype, order='C'):
|
|
# Make array shape `shape` with aligned at `align` bytes
|
|
d = dtype()
|
|
# Make array of correct size with `align` extra bytes
|
|
N = np.prod(shape)
|
|
tmp = np.zeros(N * d.nbytes + align, dtype=np.uint8)
|
|
address = tmp.__array_interface__["data"][0]
|
|
# Find offset into array giving desired alignment
|
|
for offset in range(align):
|
|
if (address + offset) % align == 0:
|
|
break
|
|
tmp = tmp[offset:offset+N*d.nbytes].view(dtype=dtype)
|
|
return tmp.reshape(shape, order=order)
|
|
|
|
def as_aligned(arr, align, dtype, order='C'):
|
|
# Copy `arr` into an aligned array with same shape
|
|
aligned = aligned_array(arr.shape, align, dtype, order)
|
|
aligned[:] = arr[:]
|
|
return aligned
|
|
|
|
def assert_dot_close(A, X, desired):
|
|
assert_allclose(self.blas_func(1.0, A, X), desired,
|
|
rtol=1e-5, atol=1e-7)
|
|
|
|
testdata = product((15, 32), (10000,), (200, 89), ('C', 'F'))
|
|
for align, m, n, a_order in testdata:
|
|
A_d = np.random.rand(m, n)
|
|
X_d = np.random.rand(n)
|
|
desired = np.dot(A_d, X_d)
|
|
# Calculation with aligned single precision
|
|
A_f = as_aligned(A_d, align, np.float32, order=a_order)
|
|
X_f = as_aligned(X_d, align, np.float32, order=a_order)
|
|
assert_dot_close(A_f, X_f, desired)
|
|
|
|
except AttributeError:
|
|
class TestSgemv:
|
|
pass
|
|
|
|
|
|
class TestDgemv(BaseGemv):
|
|
blas_func = fblas.dgemv
|
|
dtype = float64
|
|
|
|
|
|
try:
|
|
class TestCgemv(BaseGemv):
|
|
blas_func = fblas.cgemv
|
|
dtype = complex64
|
|
except AttributeError:
|
|
class TestCgemv:
|
|
pass
|
|
|
|
|
|
class TestZgemv(BaseGemv):
|
|
blas_func = fblas.zgemv
|
|
dtype = complex128
|
|
|
|
|
|
"""
|
|
##################################################
|
|
### Test blas ?ger
|
|
### This will be a mess to test all cases.
|
|
|
|
class BaseGer:
|
|
def get_data(self,x_stride=1,y_stride=1):
|
|
from numpy.random import normal, seed
|
|
seed(1234)
|
|
alpha = array(1., dtype = self.dtype)
|
|
a = normal(0.,1.,(3,3)).astype(self.dtype)
|
|
x = arange(shape(a)[0]*x_stride,dtype=self.dtype)
|
|
y = arange(shape(a)[1]*y_stride,dtype=self.dtype)
|
|
return alpha,a,x,y
|
|
def test_simple(self):
|
|
alpha,a,x,y = self.get_data()
|
|
# tranpose takes care of Fortran vs. C(and Python) memory layout
|
|
desired_a = alpha*transpose(x[:,newaxis]*y) + a
|
|
self.blas_func(x,y,a)
|
|
assert_array_almost_equal(desired_a,a)
|
|
def test_x_stride(self):
|
|
alpha,a,x,y = self.get_data(x_stride=2)
|
|
desired_a = alpha*transpose(x[::2,newaxis]*y) + a
|
|
self.blas_func(x,y,a,incx=2)
|
|
assert_array_almost_equal(desired_a,a)
|
|
def test_x_stride_assert(self):
|
|
alpha,a,x,y = self.get_data(x_stride=2)
|
|
with pytest.raises(ValueError, match='foo'):
|
|
self.blas_func(x,y,a,incx=3)
|
|
def test_y_stride(self):
|
|
alpha,a,x,y = self.get_data(y_stride=2)
|
|
desired_a = alpha*transpose(x[:,newaxis]*y[::2]) + a
|
|
self.blas_func(x,y,a,incy=2)
|
|
assert_array_almost_equal(desired_a,a)
|
|
|
|
def test_y_stride_assert(self):
|
|
alpha,a,x,y = self.get_data(y_stride=2)
|
|
with pytest.raises(ValueError, match='foo'):
|
|
self.blas_func(a,x,y,incy=3)
|
|
|
|
class TestSger(BaseGer):
|
|
blas_func = fblas.sger
|
|
dtype = float32
|
|
class TestDger(BaseGer):
|
|
blas_func = fblas.dger
|
|
dtype = float64
|
|
"""
|
|
##################################################
|
|
# Test blas ?gerc
|
|
# This will be a mess to test all cases.
|
|
|
|
"""
|
|
class BaseGerComplex(BaseGer):
|
|
def get_data(self,x_stride=1,y_stride=1):
|
|
from numpy.random import normal, seed
|
|
seed(1234)
|
|
alpha = array(1+1j, dtype = self.dtype)
|
|
a = normal(0.,1.,(3,3)).astype(self.dtype)
|
|
a = a + normal(0.,1.,(3,3)) * array(1j, dtype = self.dtype)
|
|
x = normal(0.,1.,shape(a)[0]*x_stride).astype(self.dtype)
|
|
x = x + x * array(1j, dtype = self.dtype)
|
|
y = normal(0.,1.,shape(a)[1]*y_stride).astype(self.dtype)
|
|
y = y + y * array(1j, dtype = self.dtype)
|
|
return alpha,a,x,y
|
|
def test_simple(self):
|
|
alpha,a,x,y = self.get_data()
|
|
# tranpose takes care of Fortran vs. C(and Python) memory layout
|
|
a = a * array(0.,dtype = self.dtype)
|
|
#desired_a = alpha*transpose(x[:,newaxis]*self.transform(y)) + a
|
|
desired_a = alpha*transpose(x[:,newaxis]*y) + a
|
|
#self.blas_func(x,y,a,alpha = alpha)
|
|
fblas.cgeru(x,y,a,alpha = alpha)
|
|
assert_array_almost_equal(desired_a,a)
|
|
|
|
#def test_x_stride(self):
|
|
# alpha,a,x,y = self.get_data(x_stride=2)
|
|
# desired_a = alpha*transpose(x[::2,newaxis]*self.transform(y)) + a
|
|
# self.blas_func(x,y,a,incx=2)
|
|
# assert_array_almost_equal(desired_a,a)
|
|
#def test_y_stride(self):
|
|
# alpha,a,x,y = self.get_data(y_stride=2)
|
|
# desired_a = alpha*transpose(x[:,newaxis]*self.transform(y[::2])) + a
|
|
# self.blas_func(x,y,a,incy=2)
|
|
# assert_array_almost_equal(desired_a,a)
|
|
|
|
class TestCgeru(BaseGerComplex):
|
|
blas_func = fblas.cgeru
|
|
dtype = complex64
|
|
def transform(self,x):
|
|
return x
|
|
class TestZgeru(BaseGerComplex):
|
|
blas_func = fblas.zgeru
|
|
dtype = complex128
|
|
def transform(self,x):
|
|
return x
|
|
|
|
class TestCgerc(BaseGerComplex):
|
|
blas_func = fblas.cgerc
|
|
dtype = complex64
|
|
def transform(self,x):
|
|
return conjugate(x)
|
|
|
|
class TestZgerc(BaseGerComplex):
|
|
blas_func = fblas.zgerc
|
|
dtype = complex128
|
|
def transform(self,x):
|
|
return conjugate(x)
|
|
"""
|