# -*- coding: utf-8 -*- import re import pytest import env # noqa: F401 from pybind11_tests import numpy_dtypes as m np = pytest.importorskip("numpy") @pytest.fixture(scope="module") def simple_dtype(): ld = np.dtype("longdouble") return np.dtype( { "names": ["bool_", "uint_", "float_", "ldbl_"], "formats": ["?", "u4", "f4", "f{}".format(ld.itemsize)], "offsets": [0, 4, 8, (16 if ld.alignment > 4 else 12)], } ) @pytest.fixture(scope="module") def packed_dtype(): return np.dtype([("bool_", "?"), ("uint_", "u4"), ("float_", "f4"), ("ldbl_", "g")]) def dt_fmt(): from sys import byteorder e = "<" if byteorder == "little" else ">" return ( "{{'names':['bool_','uint_','float_','ldbl_']," " 'formats':['?','" + e + "u4','" + e + "f4','" + e + "f{}']," " 'offsets':[0,4,8,{}], 'itemsize':{}}}" ) def simple_dtype_fmt(): ld = np.dtype("longdouble") simple_ld_off = 12 + 4 * (ld.alignment > 4) return dt_fmt().format(ld.itemsize, simple_ld_off, simple_ld_off + ld.itemsize) def packed_dtype_fmt(): from sys import byteorder return "[('bool_', '?'), ('uint_', '{e}u4'), ('float_', '{e}f4'), ('ldbl_', '{e}f{}')]".format( np.dtype("longdouble").itemsize, e="<" if byteorder == "little" else ">" ) def partial_ld_offset(): return ( 12 + 4 * (np.dtype("uint64").alignment > 4) + 8 + 8 * (np.dtype("longdouble").alignment > 8) ) def partial_dtype_fmt(): ld = np.dtype("longdouble") partial_ld_off = partial_ld_offset() return dt_fmt().format(ld.itemsize, partial_ld_off, partial_ld_off + ld.itemsize) def partial_nested_fmt(): ld = np.dtype("longdouble") partial_nested_off = 8 + 8 * (ld.alignment > 8) partial_ld_off = partial_ld_offset() partial_nested_size = partial_nested_off * 2 + partial_ld_off + ld.itemsize return "{{'names':['a'], 'formats':[{}], 'offsets':[{}], 'itemsize':{}}}".format( partial_dtype_fmt(), partial_nested_off, partial_nested_size ) def assert_equal(actual, expected_data, expected_dtype): np.testing.assert_equal(actual, np.array(expected_data, dtype=expected_dtype)) def test_format_descriptors(): with pytest.raises(RuntimeError) as excinfo: m.get_format_unbound() assert re.match( "^NumPy type info missing for .*UnboundStruct.*$", str(excinfo.value) ) ld = np.dtype("longdouble") ldbl_fmt = ("4x" if ld.alignment > 4 else "") + ld.char ss_fmt = "^T{?:bool_:3xI:uint_:f:float_:" + ldbl_fmt + ":ldbl_:}" dbl = np.dtype("double") partial_fmt = ( "^T{?:bool_:3xI:uint_:f:float_:" + str(4 * (dbl.alignment > 4) + dbl.itemsize + 8 * (ld.alignment > 8)) + "xg:ldbl_:}" ) nested_extra = str(max(8, ld.alignment)) assert m.print_format_descriptors() == [ ss_fmt, "^T{?:bool_:I:uint_:f:float_:g:ldbl_:}", "^T{" + ss_fmt + ":a:^T{?:bool_:I:uint_:f:float_:g:ldbl_:}:b:}", partial_fmt, "^T{" + nested_extra + "x" + partial_fmt + ":a:" + nested_extra + "x}", "^T{3s:a:3s:b:}", "^T{(3)4s:a:(2)i:b:(3)B:c:1x(4, 2)f:d:}", "^T{q:e1:B:e2:}", "^T{Zf:cflt:Zd:cdbl:}", ] def test_dtype(simple_dtype): from sys import byteorder e = "<" if byteorder == "little" else ">" assert m.print_dtypes() == [ simple_dtype_fmt(), packed_dtype_fmt(), "[('a', {}), ('b', {})]".format(simple_dtype_fmt(), packed_dtype_fmt()), partial_dtype_fmt(), partial_nested_fmt(), "[('a', 'S3'), ('b', 'S3')]", ( "{{'names':['a','b','c','d'], " + "'formats':[('S4', (3,)),('" + e + "i4', (2,)),('u1', (3,)),('" + e + "f4', (4, 2))], " + "'offsets':[0,12,20,24], 'itemsize':56}}" ).format(e=e), "[('e1', '" + e + "i8'), ('e2', 'u1')]", "[('x', 'i1'), ('y', '" + e + "u8')]", "[('cflt', '" + e + "c8'), ('cdbl', '" + e + "c16')]", ] d1 = np.dtype( { "names": ["a", "b"], "formats": ["int32", "float64"], "offsets": [1, 10], "itemsize": 20, } ) d2 = np.dtype([("a", "i4"), ("b", "f4")]) assert m.test_dtype_ctors() == [ np.dtype("int32"), np.dtype("float64"), np.dtype("bool"), d1, d1, np.dtype("uint32"), d2, ] assert m.test_dtype_methods() == [ np.dtype("int32"), simple_dtype, False, True, np.dtype("int32").itemsize, simple_dtype.itemsize, ] assert m.trailing_padding_dtype() == m.buffer_to_dtype( np.zeros(1, m.trailing_padding_dtype()) ) def test_recarray(simple_dtype, packed_dtype): elements = [(False, 0, 0.0, -0.0), (True, 1, 1.5, -2.5), (False, 2, 3.0, -5.0)] for func, dtype in [ (m.create_rec_simple, simple_dtype), (m.create_rec_packed, packed_dtype), ]: arr = func(0) assert arr.dtype == dtype assert_equal(arr, [], simple_dtype) assert_equal(arr, [], packed_dtype) arr = func(3) assert arr.dtype == dtype assert_equal(arr, elements, simple_dtype) assert_equal(arr, elements, packed_dtype) # Show what recarray's look like in NumPy. assert type(arr[0]) == np.void assert type(arr[0].item()) == tuple if dtype == simple_dtype: assert m.print_rec_simple(arr) == [ "s:0,0,0,-0", "s:1,1,1.5,-2.5", "s:0,2,3,-5", ] else: assert m.print_rec_packed(arr) == [ "p:0,0,0,-0", "p:1,1,1.5,-2.5", "p:0,2,3,-5", ] nested_dtype = np.dtype([("a", simple_dtype), ("b", packed_dtype)]) arr = m.create_rec_nested(0) assert arr.dtype == nested_dtype assert_equal(arr, [], nested_dtype) arr = m.create_rec_nested(3) assert arr.dtype == nested_dtype assert_equal( arr, [ ((False, 0, 0.0, -0.0), (True, 1, 1.5, -2.5)), ((True, 1, 1.5, -2.5), (False, 2, 3.0, -5.0)), ((False, 2, 3.0, -5.0), (True, 3, 4.5, -7.5)), ], nested_dtype, ) assert m.print_rec_nested(arr) == [ "n:a=s:0,0,0,-0;b=p:1,1,1.5,-2.5", "n:a=s:1,1,1.5,-2.5;b=p:0,2,3,-5", "n:a=s:0,2,3,-5;b=p:1,3,4.5,-7.5", ] arr = m.create_rec_partial(3) assert str(arr.dtype) == partial_dtype_fmt() partial_dtype = arr.dtype assert "" not in arr.dtype.fields assert partial_dtype.itemsize > simple_dtype.itemsize assert_equal(arr, elements, simple_dtype) assert_equal(arr, elements, packed_dtype) arr = m.create_rec_partial_nested(3) assert str(arr.dtype) == partial_nested_fmt() assert "" not in arr.dtype.fields assert "" not in arr.dtype.fields["a"][0].fields assert arr.dtype.itemsize > partial_dtype.itemsize np.testing.assert_equal(arr["a"], m.create_rec_partial(3)) def test_array_constructors(): data = np.arange(1, 7, dtype="int32") for i in range(8): np.testing.assert_array_equal(m.test_array_ctors(10 + i), data.reshape((3, 2))) np.testing.assert_array_equal(m.test_array_ctors(20 + i), data.reshape((3, 2))) for i in range(5): np.testing.assert_array_equal(m.test_array_ctors(30 + i), data) np.testing.assert_array_equal(m.test_array_ctors(40 + i), data) def test_string_array(): arr = m.create_string_array(True) assert str(arr.dtype) == "[('a', 'S3'), ('b', 'S3')]" assert m.print_string_array(arr) == [ "a='',b=''", "a='a',b='a'", "a='ab',b='ab'", "a='abc',b='abc'", ] dtype = arr.dtype assert arr["a"].tolist() == [b"", b"a", b"ab", b"abc"] assert arr["b"].tolist() == [b"", b"a", b"ab", b"abc"] arr = m.create_string_array(False) assert dtype == arr.dtype def test_array_array(): from sys import byteorder e = "<" if byteorder == "little" else ">" arr = m.create_array_array(3) assert str(arr.dtype) == ( "{{'names':['a','b','c','d'], " + "'formats':[('S4', (3,)),('" + e + "i4', (2,)),('u1', (3,)),('{e}f4', (4, 2))], " + "'offsets':[0,12,20,24], 'itemsize':56}}" ).format(e=e) assert m.print_array_array(arr) == [ "a={{A,B,C,D},{K,L,M,N},{U,V,W,X}},b={0,1}," + "c={0,1,2},d={{0,1},{10,11},{20,21},{30,31}}", "a={{W,X,Y,Z},{G,H,I,J},{Q,R,S,T}},b={1000,1001}," + "c={10,11,12},d={{100,101},{110,111},{120,121},{130,131}}", "a={{S,T,U,V},{C,D,E,F},{M,N,O,P}},b={2000,2001}," + "c={20,21,22},d={{200,201},{210,211},{220,221},{230,231}}", ] assert arr["a"].tolist() == [ [b"ABCD", b"KLMN", b"UVWX"], [b"WXYZ", b"GHIJ", b"QRST"], [b"STUV", b"CDEF", b"MNOP"], ] assert arr["b"].tolist() == [[0, 1], [1000, 1001], [2000, 2001]] assert m.create_array_array(0).dtype == arr.dtype def test_enum_array(): from sys import byteorder e = "<" if byteorder == "little" else ">" arr = m.create_enum_array(3) dtype = arr.dtype assert dtype == np.dtype([("e1", e + "i8"), ("e2", "u1")]) assert m.print_enum_array(arr) == ["e1=A,e2=X", "e1=B,e2=Y", "e1=A,e2=X"] assert arr["e1"].tolist() == [-1, 1, -1] assert arr["e2"].tolist() == [1, 2, 1] assert m.create_enum_array(0).dtype == dtype def test_complex_array(): from sys import byteorder e = "<" if byteorder == "little" else ">" arr = m.create_complex_array(3) dtype = arr.dtype assert dtype == np.dtype([("cflt", e + "c8"), ("cdbl", e + "c16")]) assert m.print_complex_array(arr) == [ "c:(0,0.25),(0.5,0.75)", "c:(1,1.25),(1.5,1.75)", "c:(2,2.25),(2.5,2.75)", ] assert arr["cflt"].tolist() == [0.0 + 0.25j, 1.0 + 1.25j, 2.0 + 2.25j] assert arr["cdbl"].tolist() == [0.5 + 0.75j, 1.5 + 1.75j, 2.5 + 2.75j] assert m.create_complex_array(0).dtype == dtype def test_signature(doc): assert ( doc(m.create_rec_nested) == "create_rec_nested(arg0: int) -> numpy.ndarray[NestedStruct]" ) def test_scalar_conversion(): n = 3 arrays = [ m.create_rec_simple(n), m.create_rec_packed(n), m.create_rec_nested(n), m.create_enum_array(n), ] funcs = [m.f_simple, m.f_packed, m.f_nested] for i, func in enumerate(funcs): for j, arr in enumerate(arrays): if i == j and i < 2: assert [func(arr[k]) for k in range(n)] == [k * 10 for k in range(n)] else: with pytest.raises(TypeError) as excinfo: func(arr[0]) assert "incompatible function arguments" in str(excinfo.value) def test_vectorize(): n = 3 array = m.create_rec_simple(n) values = m.f_simple_vectorized(array) np.testing.assert_array_equal(values, [0, 10, 20]) array_2 = m.f_simple_pass_thru_vectorized(array) np.testing.assert_array_equal(array, array_2) def test_cls_and_dtype_conversion(simple_dtype): s = m.SimpleStruct() assert s.astuple() == (False, 0, 0.0, 0.0) assert m.SimpleStruct.fromtuple(s.astuple()).astuple() == s.astuple() s.uint_ = 2 assert m.f_simple(s) == 20 # Try as recarray of shape==(1,). s_recarray = np.array([(False, 2, 0.0, 0.0)], dtype=simple_dtype) # Show that this will work for vectorized case. np.testing.assert_array_equal(m.f_simple_vectorized(s_recarray), [20]) # Show as a scalar that inherits from np.generic. s_scalar = s_recarray[0] assert isinstance(s_scalar, np.void) assert m.f_simple(s_scalar) == 20 # Show that an *array* scalar (np.ndarray.shape == ()) does not convert. # More specifically, conversion to SimpleStruct is not implicit. s_recarray_scalar = s_recarray.reshape(()) assert isinstance(s_recarray_scalar, np.ndarray) assert s_recarray_scalar.dtype == simple_dtype with pytest.raises(TypeError) as excinfo: m.f_simple(s_recarray_scalar) assert "incompatible function arguments" in str(excinfo.value) # Explicitly convert to m.SimpleStruct. assert m.f_simple(m.SimpleStruct.fromtuple(s_recarray_scalar.item())) == 20 # Show that an array of dtype=object does *not* convert. s_array_object = np.array([s]) assert s_array_object.dtype == object with pytest.raises(TypeError) as excinfo: m.f_simple_vectorized(s_array_object) assert "incompatible function arguments" in str(excinfo.value) # Explicitly convert to `np.array(..., dtype=simple_dtype)` s_array = np.array([s.astuple()], dtype=simple_dtype) np.testing.assert_array_equal(m.f_simple_vectorized(s_array), [20]) def test_register_dtype(): with pytest.raises(RuntimeError) as excinfo: m.register_dtype() assert "dtype is already registered" in str(excinfo.value) @pytest.mark.xfail("env.PYPY") def test_str_leak(): from sys import getrefcount fmt = "f4" pytest.gc_collect() start = getrefcount(fmt) d = m.dtype_wrapper(fmt) assert d is np.dtype("f4") del d pytest.gc_collect() assert getrefcount(fmt) == start def test_compare_buffer_info(): assert all(m.compare_buffer_info())