diff --git a/cuvec/CMakeLists.txt b/cuvec/CMakeLists.txt index 153d5c3..d893d4c 100644 --- a/cuvec/CMakeLists.txt +++ b/cuvec/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.24 FATAL_ERROR) if(NOT DEFINED SKBUILD_PROJECT_VERSION) - set(SKBUILD_PROJECT_VERSION 4 CACHE STRING "version" FORCE) + set(SKBUILD_PROJECT_VERSION 6 CACHE STRING "version" FORCE) endif() string(REGEX REPLACE [[([0-9]+)\.([0-9]+)\.([0-9]+).*]] [[\1.\2.\3]] SKBUILD_PROJECT_VERSION "${SKBUILD_PROJECT_VERSION}") project(cuvec LANGUAGES C CXX VERSION "${SKBUILD_PROJECT_VERSION}") diff --git a/cuvec/__init__.py b/cuvec/__init__.py index e211d1d..493b6df 100644 --- a/cuvec/__init__.py +++ b/cuvec/__init__.py @@ -3,8 +3,8 @@ Python buffered array -> C++11 `std::vector` -> CUDA managed memory. """ -__author__ = "Casper O. da Costa-Luis" -__date__ = "2021" +__author__ = "Casper da Costa-Luis (https://github.com/casperdcl)" +__date__ = "2021-2024" # version detector. Precedence: installed dist, git, 'UNKNOWN' try: from ._dist_ver import __version__ diff --git a/cuvec/cpython.py b/cuvec/cpython.py index 0e9ad73..a6d53b3 100644 --- a/cuvec/cpython.py +++ b/cuvec/cpython.py @@ -11,6 +11,7 @@ __all__ = [ 'CuVec', 'zeros', 'ones', 'zeros_like', 'ones_like', 'copy', 'asarray', 'Shape', 'typecodes'] +__author__, __date__, __version__ = cu.__author__, cu.__date__, cu.__version__ log = logging.getLogger(__name__) vec_types = { diff --git a/cuvec/include/cuvec.cuh b/cuvec/include/cuvec.cuh index 721f82e..9b69804 100644 --- a/cuvec/include/cuvec.cuh +++ b/cuvec/include/cuvec.cuh @@ -112,6 +112,13 @@ template struct NDCuVec { if (size != vec.size()) throw std::length_error("reshape: size mismatch"); this->shape = shape; } + std::vector strides() const { + const size_t ndim = this->shape.size(); + std::vector s(ndim); + s[ndim - 1] = sizeof(T); + for (int i = ndim - 2; i >= 0; i--) s[i] = this->shape[i + 1] * s[i + 1]; + return s; + } }; #endif // _CUVEC_H_ diff --git a/cuvec/include/cuvec_pybind11.cuh b/cuvec/include/cuvec_pybind11.cuh index 164eaf6..999be18 100644 --- a/cuvec/include/cuvec_pybind11.cuh +++ b/cuvec/include/cuvec_pybind11.cuh @@ -21,12 +21,21 @@ PYBIND11_MAKE_OPAQUE(NDCuVec); PYBIND11_MAKE_OPAQUE(NDCuVec); #ifdef _CUVEC_HALF PYBIND11_MAKE_OPAQUE(NDCuVec<_CUVEC_HALF>); +template <> struct pybind11::format_descriptor<_CUVEC_HALF> : pybind11::format_descriptor { + static std::string format() { return "e"; } +}; #endif PYBIND11_MAKE_OPAQUE(NDCuVec); PYBIND11_MAKE_OPAQUE(NDCuVec); #define PYBIND11_BIND_NDCUVEC(T, typechar) \ - pybind11::class_>(m, PYBIND11_TOSTRING(NDCuVec_##typechar)) \ + pybind11::class_>(m, PYBIND11_TOSTRING(NDCuVec_##typechar), \ + pybind11::buffer_protocol()) \ + .def_buffer([](NDCuVec &v) -> pybind11::buffer_info { \ + return pybind11::buffer_info(v.vec.data(), sizeof(T), \ + pybind11::format_descriptor::format(), v.shape.size(), \ + v.shape, v.strides()); \ + }) \ .def(pybind11::init<>()) \ .def(pybind11::init>()) \ .def_property( \ diff --git a/cuvec/pybind11.py b/cuvec/pybind11.py index 430f0a2..c0f120e 100644 --- a/cuvec/pybind11.py +++ b/cuvec/pybind11.py @@ -4,79 +4,85 @@ A pybind11-driven equivalent of the CPython Extension API-driven `cpython.py` """ import logging -import re from collections.abc import Sequence -from functools import partial from textwrap import dedent -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, Tuple import numpy as np from . import cuvec_pybind11 as cu # type: ignore [attr-defined] # yapf: disable -from ._utils import CVector, Shape, _generate_helpers, typecodes +from ._utils import Shape, _generate_helpers, typecodes __all__ = [ - 'CuVec', 'zeros', 'ones', 'zeros_like', 'ones_like', 'copy', 'asarray', 'retarray', 'Shape', - 'typecodes'] + 'CuVec', 'zeros', 'ones', 'zeros_like', 'ones_like', 'copy', 'asarray', 'Shape', 'typecodes'] +__author__, __date__, __version__ = cu.__author__, cu.__date__, cu.__version__ log = logging.getLogger(__name__) +vec_types = { + np.dtype('int8'): cu.NDCuVec_b, + np.dtype('uint8'): cu.NDCuVec_B, + np.dtype('S1'): cu.NDCuVec_c, + np.dtype('int16'): cu.NDCuVec_h, + np.dtype('uint16'): cu.NDCuVec_H, + np.dtype('int32'): cu.NDCuVec_i, + np.dtype('uint32'): cu.NDCuVec_I, + np.dtype('int64'): cu.NDCuVec_q, + np.dtype('uint64'): cu.NDCuVec_Q, + np.dtype('float32'): cu.NDCuVec_f, + np.dtype('float64'): cu.NDCuVec_d} if hasattr(cu, 'NDCuVec_e'): typecodes += 'e' + vec_types[np.dtype('float16')] = cu.NDCuVec_e -class Pybind11Vector(CVector): - RE_CUVEC_TYPE = re.compile(r"<.*NDCuVec_(.) object at 0x\w+>") - - def __init__(self, typechar: str, shape: Shape, cuvec=None): - """ - Args: - typechar(char) - shape(tuple(int)) - cuvec(NDCuVec): if given, `typechar` and `shape` are ignored - """ - if cuvec is None: - shape = shape if isinstance(shape, Sequence) else (shape,) - cuvec = getattr(cu, f'NDCuVec_{typechar}')(shape) - else: - typechar = self.is_raw_cuvec(cuvec).group(1) - self.cuvec = cuvec - super().__init__(typechar) +def cu_zeros(shape: Shape, dtype="float32"): + """ + Returns a new `` of the specified shape and data type. + """ + return vec_types[np.dtype(dtype)](shape if isinstance(shape, Sequence) else (shape,)) - @property - def shape(self) -> tuple: - return tuple(self.cuvec.shape) - @shape.setter - def shape(self, shape: Shape): - shape = shape if isinstance(shape, Sequence) else (shape,) - self.cuvec.shape = shape +def cu_copy(arr): + """ + Returns a new `` with data copied from the specified `arr`. + """ + res = cu_zeros(arr.shape, arr.dtype) + np.asarray(res).flat = arr.flat + return res + + +_NDCuVec_types = tuple(vec_types.values()) +_NDCuVec_types_s = tuple(map(str, vec_types.values())) - @property - def address(self) -> int: - return self.cuvec.address +def is_raw_cuvec(cuvec): + """ + Returns `True` when given the output of + pybind11 API functions returning `NDCuVec *` PyObjects. -Pybind11Vector.vec_types = {np.dtype(c): partial(Pybind11Vector, c) for c in typecodes} + This is needed since conversely `isinstance(cuvec, CuVec)` may be `False` + due to external libraries + `#include "cuvec_pybind11.cuh"` making a distinct type object. + """ + return isinstance(cuvec, _NDCuVec_types) or str(type(cuvec)) in _NDCuVec_types_s class CuVec(np.ndarray): """ A `numpy.ndarray` compatible view with a `cuvec` member containing the - underlying `Pybind11Vector` object (for use in pybind11 API function calls). + underlying `cuvec.cuvec_pybind11.NDCuVec_*` object (for use in pybind11 API function calls). """ def __new__(cls, arr): - """arr: `cuvec.pybind11.CuVec`, raw `Pybind11Vector`, or `numpy.ndarray`""" - if Pybind11Vector.is_instance(arr): - log.debug("wrap pyraw %s", type(arr)) + """arr: `cuvec.pybind11.CuVec`, raw `cuvec.cuvec_pybind11.NDCuVec_*`, or `numpy.ndarray`""" + if is_raw_cuvec(arr): + log.debug("wrap raw %s", type(arr)) obj = np.asarray(arr).view(cls) - obj._vec = arr - obj.cuvec = arr.cuvec + obj.cuvec = arr return obj - if isinstance(arr, CuVec) and hasattr(arr, '_vec'): + if isinstance(arr, CuVec) and hasattr(arr, 'cuvec'): log.debug("new view") obj = np.asarray(arr).view(cls) - obj._vec = arr._vec - obj.cuvec = arr._vec.cuvec + obj.cuvec = arr.cuvec return obj if isinstance(arr, np.ndarray): log.debug("copy") @@ -97,12 +103,14 @@ def __cuda_array_interface__(self) -> Dict[str, Any]: raise AttributeError( dedent("""\ `numpy.ndarray` object has no attribute `cuvec`: - try using `cuvec.asarray()` first.""")) - return self._vec.__cuda_array_interface__ + try using `cuvec.pybind11.asarray()` first.""")) + res = self.__array_interface__ + return { + 'shape': res['shape'], 'typestr': res['typestr'], 'data': res['data'], 'version': 3} def resize(self, new_shape: Shape): """Change shape (but not size) of array in-place.""" - self._vec.shape = new_shape + self.cuvec.shape = new_shape if isinstance(new_shape, Sequence) else (new_shape,) super().resize(new_shape, refcheck=False) @property @@ -119,7 +127,7 @@ def zeros(shape: Shape, dtype="float32") -> CuVec: Returns a `cuvec.pybind11.CuVec` view of a new `numpy.ndarray` of the specified shape and data type (`cuvec` equivalent of `numpy.zeros`). """ - return CuVec(Pybind11Vector.zeros(shape, dtype)) + return CuVec(cu_zeros(shape, dtype)) ones, zeros_like, ones_like = _generate_helpers(zeros, CuVec) @@ -131,45 +139,16 @@ def copy(arr) -> CuVec: with data copied from the specified `arr` (`cuvec` equivalent of `numpy.copy`). """ - return CuVec(Pybind11Vector.copy(arr)) + return CuVec(cu_copy(arr)) -def asarray(arr, dtype=None, order=None, ownership: str = 'warning') -> CuVec: +def asarray(arr, dtype=None, order=None) -> CuVec: """ Returns a `cuvec.pybind11.CuVec` view of `arr`, avoiding memory copies if possible. (`cuvec` equivalent of `numpy.asarray`). - - Args: - ownership: logging level if `is_raw_cuvec(arr)`. - WARNING: `asarray()` should not be used on an existing reference, e.g.: - >>> res = asarray(some_pybind11_api_func(..., output=getattr(out, 'cuvec', None))) - `res.cuvec` and `out.cuvec` are now the same - yet garbage collected separately (dangling ptr). - Instead, use the `retarray` helper: - >>> raw = some_pybind11_api_func(..., output=getattr(out, 'cuvec', None)) - >>> res = retarray(raw, out) - NB: `asarray()`/`retarray()` are safe if the raw cuvec was created in C++, e.g.: - >>> res = retarray(some_pybind11_api_func(..., output=None)) """ - if Pybind11Vector.is_raw_cuvec(arr): - ownership = ownership.lower() - if ownership in {'critical', 'fatal', 'error'}: - raise IOError("Can't take ownership of existing cuvec (would create dangling ptr)") - getattr(log, ownership)("taking ownership") - arr = Pybind11Vector('', (), arr) - if not isinstance(arr, np.ndarray) and Pybind11Vector.is_instance(arr): + if not isinstance(arr, np.ndarray) and is_raw_cuvec(arr): res = CuVec(arr) if dtype is None or res.dtype == np.dtype(dtype): return CuVec(np.asanyarray(res, order=order)) return CuVec(np.asanyarray(arr, dtype=dtype, order=order)) - - -def retarray(raw, out: Optional[CuVec] = None): - """ - Returns `out if hasattr(out, 'cuvec') else asarray(raw, ownership='debug')`. - See `asarray` for explanation. - Args: - raw: a raw CuVec (returned by C++/pybind11 function). - out: preallocated output array. - """ - return out if hasattr(out, 'cuvec') else asarray(raw, ownership='debug') diff --git a/cuvec/src/cpython.cu b/cuvec/src/cpython.cu index 3d77883..593fd6d 100644 --- a/cuvec/src/cpython.cu +++ b/cuvec/src/cpython.cu @@ -2,8 +2,6 @@ * Unifying Python/C++/CUDA memory. * * Python buffered array -> C++11 `std::vector` -> CUDA managed memory. - * - * Copyright (2021) Casper da Costa-Luis */ #include "cuvec_cpython.cuh" // PyCuVec, PyCuVec_tp #include @@ -84,7 +82,7 @@ PyMODINIT_FUNC PyInit_cuvec_cpython(void) { if (author == NULL) return NULL; PyModule_AddObject(m, "__author__", author); - PyObject *date = Py_BuildValue("s", "2021"); + PyObject *date = Py_BuildValue("s", "2021-2024"); if (date == NULL) return NULL; PyModule_AddObject(m, "__date__", date); diff --git a/cuvec/src/cuvec_swig.i b/cuvec/src/cuvec_swig.i index 6da7c73..9ca0d33 100644 --- a/cuvec/src/cuvec_swig.i +++ b/cuvec/src/cuvec_swig.i @@ -56,3 +56,12 @@ MKCUVEC(_CUVEC_HALF, e) #endif MKCUVEC(float, f) MKCUVEC(double, d) + +%{ +static const char __author__[] = "Casper da Costa-Luis (https://github.com/casperdcl)"; +static const char __date__[] = "2021-2024"; +static const char __version__[] = "4.0.0"; +%} +static const char __author__[]; +static const char __date__[]; +static const char __version__[]; diff --git a/cuvec/src/example_cpython/example_mod.cu b/cuvec/src/example_cpython/example_mod.cu index df3bec4..f50f335 100644 --- a/cuvec/src/example_cpython/example_mod.cu +++ b/cuvec/src/example_cpython/example_mod.cu @@ -1,7 +1,7 @@ /** * Example external extension module using CuVec. * - * Copyright (2021) Casper da Costa-Luis + * Copyright (2021-2024) Casper da Costa-Luis */ #include "Python.h" #include "cuvec_cpython.cuh" // PyCuVec diff --git a/cuvec/src/example_pybind11/example_pybind11.cu b/cuvec/src/example_pybind11/example_pybind11.cu index f26e086..99f2c94 100644 --- a/cuvec/src/example_pybind11/example_pybind11.cu +++ b/cuvec/src/example_pybind11/example_pybind11.cu @@ -1,7 +1,7 @@ /** * Example external pybind11 extension module using CuVec. * - * Copyright (2021) Casper da Costa-Luis + * Copyright (2024) Casper da Costa-Luis */ #include "cuvec.cuh" // NDCuVec #include // pybind11, PYBIND11_MODULE diff --git a/cuvec/src/example_swig/example_swig.cu b/cuvec/src/example_swig/example_swig.cu index e2e1486..4bfc202 100644 --- a/cuvec/src/example_swig/example_swig.cu +++ b/cuvec/src/example_swig/example_swig.cu @@ -1,7 +1,7 @@ /** * Example external SWIG extension module using CuVec. * - * Copyright (2021) Casper da Costa-Luis + * Copyright (2021-2024) Casper da Costa-Luis */ #include "cuvec.cuh" // NDCuVec #include // std::length_error diff --git a/cuvec/src/pybind11.cu b/cuvec/src/pybind11.cu index fcdd4e2..064c30b 100644 --- a/cuvec/src/pybind11.cu +++ b/cuvec/src/pybind11.cu @@ -2,8 +2,6 @@ * Unifying Python/C++/CUDA memory. * * pybind11 opaque vector -> C++11 `std::vector` -> CUDA managed memory. - * - * Copyright (2024) Casper da Costa-Luis */ #include "cuvec_pybind11.cuh" // PYBIND11_BIND_NDCUVEC #include // PYBIND11_MODULE @@ -27,4 +25,7 @@ PYBIND11_MODULE(cuvec_pybind11, m) { #endif PYBIND11_BIND_NDCUVEC(float, f); PYBIND11_BIND_NDCUVEC(double, d); + m.attr("__author__") = "Casper da Costa-Luis (https://github.com/casperdcl)"; + m.attr("__date__") = "2024"; + m.attr("__version__") = "2.0.0"; } diff --git a/cuvec/swig.py b/cuvec/swig.py index 55dfe06..449e84d 100644 --- a/cuvec/swig.py +++ b/cuvec/swig.py @@ -18,6 +18,7 @@ __all__ = [ 'CuVec', 'zeros', 'ones', 'zeros_like', 'ones_like', 'copy', 'asarray', 'retarray', 'Shape', 'typecodes'] +__author__, __date__, __version__ = sw.__author__, sw.__date__, sw.__version__ log = logging.getLogger(__name__) if hasattr(sw, 'NDCuVec_e_new'): diff --git a/docs/index.md b/docs/index.md index 0835189..64d6e2b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -175,7 +175,7 @@ The following involve no memory copies. === "to Python" ```py import cuvec.pybind11 as cuvec, my_custom_lib - arr = cuvec.retarray(my_custom_lib.some_pybind11_api_func()) + arr = cuvec.asarray(my_custom_lib.some_pybind11_api_func()) ``` === "to C++" @@ -245,7 +245,7 @@ Python: import cuvec.pybind11 as cuvec, numpy, mymod arr = cuvec.zeros((1337, 42, 7), "float32") assert all(numpy.mean(arr, axis=(0, 1)) == 0) - print(cuvec.retarray(mymod.myfunc(arr.cuvec)).sum()) + print(cuvec.asarray(mymod.myfunc(arr.cuvec)).sum()) ``` === "SWIG" diff --git a/pyproject.toml b/pyproject.toml index 072636f..900de9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ classifiers = [ dependencies = ['importlib_resources; python_version < "3.9"', "numpy"] [project.optional-dependencies] -dev = ["pytest>=6", "pytest-cov", "pytest-timeout", "pytest-xdist"] +dev = ["pytest>=6", "pytest-cov", "pytest-timeout", "pytest-xdist", "packaging"] [tool.mypy] [[tool.mypy.overrides]] diff --git a/tests/test_common.py b/tests/test_common.py index 7dbdc58..875f427 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -2,11 +2,15 @@ import logging import numpy as np +from packaging import version from pytest import importorskip, mark, raises, skip import cuvec as cu import cuvec.cpython as cp +# `example_cpython` is defined in ../cuvec/src/example_cpython/ +from cuvec import example_cpython # type: ignore # yapf: disable + from . import shape try: @@ -19,8 +23,6 @@ try: # `cuvec.swig` alternative to `cuvec.cpython` - # `example_swig` is defined in ../cuvec/src/example_swig/ - from cuvec import example_swig # type: ignore # yapf: disable from cuvec import swig as sw except ImportError: sw, example_swig = None, None # type: ignore # yapf: disable @@ -41,14 +43,11 @@ def test_cmake_prefix(): for i in ('Config', 'ConfigVersion', 'Targets', 'Targets-relwithdebinfo')} -@mark.parametrize("cu,CVector", [(py, 'Pybind11Vector'), (sw, 'SWIGVector')]) -def test_CVector_strides(cu, CVector): - if cu is None: - skip("cuvec.pybind11 or cuvec.swig not available") - v = getattr(cu, CVector)('f', shape) - a = np.asarray(v) - assert a.shape == shape - assert a.strides == (512, 32, 4) +@mark.parametrize("cu", filter(None, [cp, py, sw])) +def test_metadata(cu): + assert isinstance(cu.__author__, str) + assert isinstance(cu.__date__, str) + assert version.parse(cu.__version__).release @mark.parametrize("spec,result", [("i", np.int32), ("d", np.float64)]) @@ -75,7 +74,7 @@ def test_copy(cu): @mark.parametrize("cu,classname", [(cp, "raw "), - (py, "pyraw "), + (py, "raw "), (sw, "swraw ")]) def test_CuVec_creation(cu, classname, caplog): if cu is None: @@ -115,53 +114,6 @@ def test_CuVec_creation(cu, classname, caplog): assert not caplog.record_tuples -@mark.parametrize("cu", filter(None, [py, sw])) -@mark.timeout(20) -def test_asarray(cu): - v = cu.asarray(np.random.random(shape)) - w = cu.CuVec(v) - assert w.cuvec == v.cuvec - assert (w == v).all() - assert str(w._vec) == str(v._vec) - assert np.asarray(w._vec).data == np.asarray(v._vec).data - x = cu.asarray(w._vec) - assert x.cuvec == v.cuvec - assert (x == v).all() - assert str(x._vec) == str(v._vec) - assert np.asarray(x._vec).data == np.asarray(v._vec).data - y = cu.asarray(x.tolist()) - assert y.cuvec != v.cuvec - assert (y == v).all() - assert str(y._vec) != str(v._vec) - assert np.asarray(y._vec).data == np.asarray(v._vec).data - z = cu.asarray(v[:]) - assert z.cuvec != v.cuvec - assert (z == v[:]).all() - assert str(z._vec) != str(v._vec) - assert np.asarray(z._vec).data == np.asarray(v._vec).data - s = cu.asarray(v[1:]) - assert s.cuvec != v.cuvec - assert (s == v[1:]).all() - assert str(s._vec) != str(v._vec) - assert np.asarray(s._vec).data != np.asarray(v._vec).data - with raises(IOError): - cu.asarray(s._vec.cuvec, ownership='error') - - -@mark.parametrize("cu", filter(None, [py, sw])) -def test_resize(cu): - v = cu.asarray(np.random.random(shape)) - v.resize(shape[::-1]) - assert v.shape == shape[::-1] - assert v._vec.shape == v.shape - v.resize(v.size) - assert v.shape == (v.size,) - assert v._vec.shape == v.shape - v.shape = shape - assert v.shape == shape - assert v._vec.shape == v.shape - - @mark.timeout(60) @mark.parametrize("cu", filter(None, [cp, py, sw])) def test_cuda_array_interface(cu): @@ -196,24 +148,67 @@ def test_cuda_array_interface(cu): ndarr.__cuda_array_interface__ -@mark.parametrize("cu,ex", [(py, example_pybind11), (sw, example_swig)]) -def test_increment(cu, ex): +@mark.parametrize("cu,tp", [(cp, 'PyCuVec_f'), (py, 'NDCuVec_f')]) +def test_CVector_strides(cu, tp): if cu is None: - skip("cuvec.pybind11 or cuvec.swig not available") + skip("cuvec.pybind11 not available") + v = getattr(cu.cu, tp)(shape) + a = np.asarray(v) + assert a.shape == shape + assert a.strides == (512, 32, 4) + + +@mark.parametrize("cu", filter(None, [cp, py])) +@mark.timeout(20) +def test_asarray(cu): + v = cu.asarray(np.random.random(shape)) + w = cu.CuVec(v) + assert w.cuvec == v.cuvec + assert (w == v).all() + assert np.asarray(w.cuvec).data == np.asarray(v.cuvec).data + x = cu.asarray(w.cuvec) + assert x.cuvec == v.cuvec + assert (x == v).all() + assert np.asarray(x.cuvec).data == np.asarray(v.cuvec).data + y = cu.asarray(x.tolist()) + assert y.cuvec != v.cuvec + assert (y == v).all() + assert np.asarray(y.cuvec).data == np.asarray(v.cuvec).data + z = cu.asarray(v[:]) + assert z.cuvec != v.cuvec + assert (z == v[:]).all() + assert np.asarray(z.cuvec).data == np.asarray(v.cuvec).data + s = cu.asarray(v[1:]) + assert s.cuvec != v.cuvec + assert (s == v[1:]).all() + assert np.asarray(s.cuvec).data != np.asarray(v.cuvec).data + + +@mark.parametrize("cu,ex,wrap", [(cp, example_cpython, True), (py, example_pybind11, False)]) +def test_increment(cu, ex, wrap): + if cu is None: + skip("cuvec.pybind11 not available") a = cu.zeros((1337, 42), 'f') assert (a == 0).all() - ex.increment2d_f(a.cuvec, a.cuvec) + res = cu.asarray(ex.increment2d_f(a.cuvec, a.cuvec)) assert (a == 1).all() + assert (res == 1).all() a[:] = 0 assert (a == 0).all() + assert (res == 0).all() - b = cu.retarray(ex.increment2d_f(a.cuvec)) - assert (b == 1).all() + res = cu.asarray(ex.increment2d_f(a if wrap else a.cuvec)) + assert (res == 1).all() - c = cu.retarray(ex.increment2d_f(b.cuvec, a.cuvec), a) - assert (a == 2).all() - assert c.cuvec == a.cuvec - assert (c == a).all() - assert str(c._vec) == str(a._vec) - assert np.asarray(c._vec).data == np.asarray(a._vec).data + +@mark.parametrize("cu,ex,wrap", [(cp, example_cpython, True), (py, example_pybind11, False)]) +def test_increment_return(cu, ex, wrap): + if cu is None: + skip("cuvec.pybind11 not available") + a = cu.zeros((1337, 42), 'f') + assert (a == 0).all() + res = cu.asarray(ex.increment2d_f(a if wrap else a.cuvec, a if wrap else a.cuvec)) + assert (a == 1).all() + del a + assert (res == 1).all() diff --git a/tests/test_cpython.py b/tests/test_cpython.py index 9cf170e..cff1450 100644 --- a/tests/test_cpython.py +++ b/tests/test_cpython.py @@ -3,8 +3,7 @@ import cuvec.cpython as cu from cuvec import cuvec_cpython - -from . import shape +from cuvec import example_cpython as ex # type: ignore # yapf: disable @mark.parametrize("tp", list(cu.typecodes)) @@ -21,73 +20,13 @@ def test_PyCuVec_asarray(tp): del a, b, v -def test_CVector_strides(): - v = cuvec_cpython.PyCuVec_f(shape) - a = np.asarray(v) - assert a.shape == shape - assert a.strides == (512, 32, 4) - - -@mark.timeout(20) -def test_asarray(): - v = cu.asarray(np.random.random(shape)) - w = cu.CuVec(v) - assert w.cuvec == v.cuvec - assert (w == v).all() - assert np.asarray(w.cuvec).data == np.asarray(v.cuvec).data - x = cu.asarray(w.cuvec) - assert x.cuvec == v.cuvec - assert (x == v).all() - assert np.asarray(x.cuvec).data == np.asarray(v.cuvec).data - y = cu.asarray(x.tolist()) - assert y.cuvec != v.cuvec - assert (y == v).all() - assert np.asarray(y.cuvec).data == np.asarray(v.cuvec).data - z = cu.asarray(v[:]) - assert z.cuvec != v.cuvec - assert (z == v[:]).all() - assert np.asarray(z.cuvec).data == np.asarray(v.cuvec).data - s = cu.asarray(v[1:]) - assert s.cuvec != v.cuvec - assert (s == v[1:]).all() - assert np.asarray(s.cuvec).data != np.asarray(v.cuvec).data - - -def test_increment(): - # `example_cpython` is defined in ../cuvec/src/example_cpython/ - from cuvec.example_cpython import increment2d_f - a = cu.zeros((1337, 42), 'f') - assert (a == 0).all() - res = cu.asarray(increment2d_f(a.cuvec, a.cuvec)) - assert (a == 1).all() - assert (res == 1).all() - - a[:] = 0 - assert (a == 0).all() - assert (res == 0).all() - - res = cu.asarray(increment2d_f(a)) - assert (res == 1).all() - - -def test_increment_return(): - from cuvec.example_cpython import increment2d_f - a = cu.zeros((1337, 42), 'f') - assert (a == 0).all() - res = cu.asarray(increment2d_f(a, a)) - assert (a == 1).all() - del a - assert (res == 1).all() - - def test_np_types(): - from cuvec.example_cpython import increment2d_f f = cu.zeros((1337, 42), 'f') d = cu.zeros((1337, 42), 'd') - cu.asarray(increment2d_f(f)) - cu.asarray(increment2d_f(f, f)) + cu.asarray(ex.increment2d_f(f)) + cu.asarray(ex.increment2d_f(f, f)) with raises(TypeError): - cu.asarray(increment2d_f(d)) + cu.asarray(ex.increment2d_f(d)) with raises(SystemError): # the TypeError is suppressed since a new output is generated - cu.asarray(increment2d_f(f, d)) + cu.asarray(ex.increment2d_f(f, d)) diff --git a/tests/test_pybind11.py b/tests/test_pybind11.py index 46ae418..9f10860 100644 --- a/tests/test_pybind11.py +++ b/tests/test_pybind11.py @@ -1,18 +1,48 @@ +import re + import numpy as np -from pytest import importorskip, mark +from pytest import importorskip, mark, raises + +from . import shape cu = importorskip("cuvec.pybind11") +cuvec_pybind11 = importorskip("cuvec.cuvec_pybind11") +ex = importorskip("cuvec.example_pybind11") @mark.parametrize("tp", list(cu.typecodes)) -def test_Pybind11Vector_asarray(tp): - v = cu.Pybind11Vector(tp, (1, 2, 3)) - assert repr(v) == f"Pybind11Vector('{tp}', (1, 2, 3))" +def test_NDCuVec_asarray(tp): + v = getattr(cuvec_pybind11, f"NDCuVec_{tp}")((1, 2, 3)) + assert re.match(f"", str(v)) a = np.asarray(v) assert not a.any() a[0, 0] = 42 b = np.asarray(v) assert (b[0, 0] == 42).all() assert not b[1:, 1:].any() - assert a.dtype == np.dtype(tp) + assert a.dtype.char == tp del a, b, v + + +def test_np_types(): + f = cu.zeros((1337, 42), 'f') + d = cu.zeros((1337, 42), 'd') + cu.asarray(ex.increment2d_f(f.cuvec)) + cu.asarray(ex.increment2d_f(f.cuvec, f.cuvec)) + with raises(TypeError): + cu.asarray(ex.increment2d_f(d.cuvec)) + with raises(TypeError): + cu.asarray(ex.increment2d_f(f.cuvec, d.cuvec)) + + +def test_resize(): + v = cu.asarray(np.random.random(shape)) + v.resize(shape[::-1]) + assert v.shape == shape[::-1] + assert v.cuvec.shape == v.shape + v.resize(v.size) + assert v.shape == (v.size,) + assert v.cuvec.shape == v.shape + v.shape = shape + assert v.shape == shape + assert v.cuvec.shape == v.shape diff --git a/tests/test_swig.py b/tests/test_swig.py index 02882ac..5339d27 100644 --- a/tests/test_swig.py +++ b/tests/test_swig.py @@ -1,7 +1,18 @@ import numpy as np -from pytest import importorskip, mark +from pytest import importorskip, mark, raises + +from . import shape cu = importorskip("cuvec.swig") +# `example_swig` is defined in ../cuvec/src/example_swig/ +ex = importorskip("cuvec.example_swig") + + +def test_SWIGVector_strides(): + v = cu.SWIGVector('f', shape) + a = np.asarray(v) + assert a.shape == shape + assert a.strides == (512, 32, 4) @mark.parametrize("tp", list(cu.typecodes)) @@ -16,3 +27,68 @@ def test_SWIGVector_asarray(tp): assert not b[1:, 1:].any() assert a.dtype == np.dtype(tp) del a, b, v + + +@mark.timeout(20) +def test_asarray(): + v = cu.asarray(np.random.random(shape)) + w = cu.CuVec(v) + assert w.cuvec == v.cuvec + assert (w == v).all() + assert str(w._vec) == str(v._vec) + assert np.asarray(w._vec).data == np.asarray(v._vec).data + x = cu.asarray(w._vec) + assert x.cuvec == v.cuvec + assert (x == v).all() + assert str(x._vec) == str(v._vec) + assert np.asarray(x._vec).data == np.asarray(v._vec).data + y = cu.asarray(x.tolist()) + assert y.cuvec != v.cuvec + assert (y == v).all() + assert str(y._vec) != str(v._vec) + assert np.asarray(y._vec).data == np.asarray(v._vec).data + z = cu.asarray(v[:]) + assert z.cuvec != v.cuvec + assert (z == v[:]).all() + assert str(z._vec) != str(v._vec) + assert np.asarray(z._vec).data == np.asarray(v._vec).data + s = cu.asarray(v[1:]) + assert s.cuvec != v.cuvec + assert (s == v[1:]).all() + assert str(s._vec) != str(v._vec) + assert np.asarray(s._vec).data != np.asarray(v._vec).data + with raises(IOError): + cu.asarray(s._vec.cuvec, ownership='error') + + +def test_resize(): + v = cu.asarray(np.random.random(shape)) + v.resize(shape[::-1]) + assert v.shape == shape[::-1] + assert v._vec.shape == v.shape + v.resize(v.size) + assert v.shape == (v.size,) + assert v._vec.shape == v.shape + v.shape = shape + assert v.shape == shape + assert v._vec.shape == v.shape + + +def test_increment(): + a = cu.zeros((1337, 42), 'f') + assert (a == 0).all() + ex.increment2d_f(a.cuvec, a.cuvec) + assert (a == 1).all() + + a[:] = 0 + assert (a == 0).all() + + b = cu.retarray(ex.increment2d_f(a.cuvec)) + assert (b == 1).all() + + c = cu.retarray(ex.increment2d_f(b.cuvec, a.cuvec), a) + assert (a == 2).all() + assert c.cuvec == a.cuvec + assert (c == a).all() + assert str(c._vec) == str(a._vec) + assert np.asarray(c._vec).data == np.asarray(a._vec).data