diff --git a/benchmarks/dgemm_compare.py b/benchmarks/dgemm_compare.py index e325e57c..5606157f 100644 --- a/benchmarks/dgemm_compare.py +++ b/benchmarks/dgemm_compare.py @@ -61,9 +61,9 @@ b_new = b c_new = c - view_a = pk.array(a_new) - view_b = pk.array(b_new) - view_c = pk.array(c_new) + view_a = pk.asarray(a_new) + view_b = pk.asarray(b_new) + view_c = pk.asarray(c_new) pk_dgemm_time_sec = timeit.timeit( "pk_dgemm(alpha, view_a, view_b, beta, view_c)", diff --git a/examples/NaiveBayes/GaussianNB.py b/examples/NaiveBayes/GaussianNB.py index 8c334f57..06348bd6 100644 --- a/examples/NaiveBayes/GaussianNB.py +++ b/examples/NaiveBayes/GaussianNB.py @@ -357,8 +357,8 @@ class labels known to the classifier. Examples -------- >>> import numpy as np - >>> X = pk.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]]) - >>> Y = pk.array([1, 1, 1, 2, 2, 2]) + >>> X = pk.asarray([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]]) + >>> Y = pk.asarray([1, 1, 1, 2, 2, 2]) >>> from sklearn.naive_bayes import GaussianNB >>> clf = GaussianNB() >>> clf.fit(X, Y) diff --git a/examples/kokkos/inclusive_scan_team_cuda.py b/examples/kokkos/inclusive_scan_team_cuda.py index d0f86a7f..a7333fb5 100644 --- a/examples/kokkos/inclusive_scan_team_cuda.py +++ b/examples/kokkos/inclusive_scan_team_cuda.py @@ -34,7 +34,7 @@ def main(): num_teams = (N + team_size - 1) // team_size view = cp.zeros(N, dtype=cp.int32) - view_pk = pk.array(view) + view_pk = pk._array(view) p_init = pk.RangePolicy(pk.ExecutionSpace.Cuda, 0, N) pk.parallel_for(p_init, init_data, view=view_pk) diff --git a/examples/pykokkos/from_array.py b/examples/pykokkos/from_array.py index ad5d53fb..b9174616 100644 --- a/examples/pykokkos/from_array.py +++ b/examples/pykokkos/from_array.py @@ -27,9 +27,9 @@ def addition_cp(i: int, cp_arr): print(f"before {cp_arr=}") print(f"before {list_arr=}") -np_view = pk.array(np_arr) -cp_view = pk.array(cp_arr) -list_view = pk.array(list_arr) +np_view = pk.asarray(np_arr) +cp_view = pk._array(cp_arr) +list_view = pk.asarray(list_arr) pk.parallel_for(pk.RangePolicy(pk.OpenMP, 0, size), addition_np, np_arr=np_view) pk.parallel_for(pk.RangePolicy(pk.Cuda, 0, size), addition_cp, cp_arr=cp_view) diff --git a/examples/pykokkos/multi_gpu.py b/examples/pykokkos/multi_gpu.py index 4f7ddc01..61298b75 100644 --- a/examples/pykokkos/multi_gpu.py +++ b/examples/pykokkos/multi_gpu.py @@ -23,14 +23,14 @@ def reduction_cp(i: int, acc: pk.Acc[int], cp_arr): pk.set_device_id(1) -cp_view_0 = pk.array(cp_arr_1) +cp_view_0 = pk._array(cp_arr_1) result_0 = pk.parallel_reduce( pk.RangePolicy(pk.Cuda, 0, size), reduction_cp, cp_arr=cp_view_0 ) print(result_0) pk.set_device_id(0) -cp_view_1 = pk.array(cp_arr_0) +cp_view_1 = pk._array(cp_arr_0) result_1 = pk.parallel_reduce( pk.RangePolicy(pk.Cuda, 0, size), reduction_cp, cp_arr=cp_view_1 ) diff --git a/pykokkos/__init__.py b/pykokkos/__init__.py index 9340945f..972a0126 100644 --- a/pykokkos/__init__.py +++ b/pykokkos/__init__.py @@ -4,6 +4,7 @@ from pykokkos.runtime import runtime_singleton from pykokkos.core import Runtime from pykokkos.interface import * +from pykokkos.interface.views import _array from pykokkos.kokkos_manager import ( initialize, finalize, diff --git a/pykokkos/interface/__init__.py b/pykokkos/interface/__init__.py index a14c2ffa..2e26edcf 100644 --- a/pykokkos/interface/__init__.py +++ b/pykokkos/interface/__init__.py @@ -98,6 +98,8 @@ ScratchView6D, ScratchView7D, ScratchView8D, + _array, + # Transitional export: deprecated, use pk.asarray for user conversions. array, asarray, result_type, diff --git a/pykokkos/interface/views.py b/pykokkos/interface/views.py index 62ed04b4..cf1886b2 100644 --- a/pykokkos/interface/views.py +++ b/pykokkos/interface/views.py @@ -5,6 +5,7 @@ from enum import Enum import os import sys +import warnings from types import ModuleType from typing import Dict, Generic, Iterator, List, Optional, Tuple, TypeVar, Union @@ -945,7 +946,7 @@ def is_array(array) -> bool: return True -def array( +def _array( array, space: Optional[MemorySpace] = None, layout: Optional[Layout] = None ) -> ViewType: """ @@ -958,8 +959,11 @@ def array( """ # if numpy array, use from_numpy() - if isinstance(array, np.ndarray) or np.isscalar(array): + if isinstance(array, np.ndarray): return from_numpy(array, space, layout) + # Python scalars do not expose .dtype, so normalize first + if np.isscalar(array): + return from_numpy(np.asarray(array), space, layout) # test if the input array can duck-type to a numpy-like array # and run from_array to preprocess the array to numpy if is_array(array): @@ -968,6 +972,25 @@ def array( return from_numpy(np.asarray(array), space, layout) +def array( + array, space: Optional[MemorySpace] = None, layout: Optional[Layout] = None +) -> ViewType: + """ + Deprecated public compatibility shim for internal array conversion. + + Prefer `pk.asarray` for user-level conversions. + """ + + warnings.warn( + "pk.array is deprecated and will be removed in a future release. " + "Use pk.asarray for user conversions; pk._array is internal/private.", + DeprecationWarning, + stacklevel=2, + ) + + return _array(array, space, layout) + + # asarray is required for comformance with the array API: # https://data-apis.org/array-api/2021.12/API_specification/creation_functions.html#objects-in-api @@ -977,7 +1000,17 @@ def asarray(obj, /, *, dtype=None, device=None, copy=None): # for now, let's cheat and use NumPy asarray() followed # by pykokkos from_numpy() - if not isinstance(obj, list) and obj in {pk.e, pk.pi, pk.inf, pk.nan}: + is_array_api_constant = False + if np.isscalar(obj): + if obj in (pk.e, pk.pi, pk.inf): + is_array_api_constant = True + else: + try: + is_array_api_constant = bool(np.isnan(obj)) + except TypeError: + is_array_api_constant = False + + if is_array_api_constant: if dtype is None: dtype = pk.float64 view = pk.View([1], dtype=dtype) diff --git a/pykokkos/lib/util.py b/pykokkos/lib/util.py index 4c959272..7898b432 100644 --- a/pykokkos/lib/util.py +++ b/pykokkos/lib/util.py @@ -11,12 +11,12 @@ def all(x, /, *, axis=None, keepdims=False): np_result = np.all(x) - ret_val = pk.array(np_result) + ret_val = pk._array(np_result) return ret_val def any(x, /, *, axis=None, keepdims=False): - return pk.View(pk.array(np.any(x))) + return pk.View(pk._array(np.any(x))) @pk.workunit diff --git a/tests/test_linalg.py b/tests/test_linalg.py index 1677bedb..99068a3c 100644 --- a/tests/test_linalg.py +++ b/tests/test_linalg.py @@ -182,12 +182,12 @@ def test_dgemm_shape(shape_a, shape_b): def test_dgemm_vs_scipy(alpha, a, b, c, beta, expected_c): # test against expected results from # scipy.linalg.blas.dgemm - view_a = pk.array(a) - view_b = pk.array(b) + view_a = pk.asarray(a) + view_b = pk.asarray(b) if c is None: view_c = None else: - view_c = pk.array(c) + view_c = pk.asarray(c) actual_c = dgemm( alpha=alpha, view_a=view_a, view_b=view_b, view_c=view_c, beta=beta ) @@ -196,7 +196,7 @@ def test_dgemm_vs_scipy(alpha, a, b, c, beta, expected_c): def test_dgemm_input_handling(): alpha = 1.0 - view_a = pk.array(np.zeros((4, 3))) - view_b = pk.array([np.array([0, 0, 0], dtype=np.int32)] * 4) + view_a = pk.asarray(np.zeros((4, 3))) + view_b = pk.asarray([np.array([0, 0, 0], dtype=np.int32)] * 4) with pytest.raises(ValueError, match="Second dimensions"): dgemm(alpha=alpha, view_a=view_a, view_b=view_b) diff --git a/tests/test_parallelreduce.py b/tests/test_parallelreduce.py index 9335ce57..d9da30b1 100644 --- a/tests/test_parallelreduce.py +++ b/tests/test_parallelreduce.py @@ -66,7 +66,7 @@ def test_squaresum_types(series_max, dtype): np_data = np.arange(series_max, dtype=dtype) expected = np.sum(np_data**2) - view = pk.array(np_data) + view = pk.asarray(np_data) policy = pk.RangePolicy(pk.ExecutionSpace.OpenMP, 0, series_max) if dtype == np.float64: diff --git a/tests/test_ufuncs.py b/tests/test_ufuncs.py index fd97c5e3..bd0899a2 100644 --- a/tests/test_ufuncs.py +++ b/tests/test_ufuncs.py @@ -259,7 +259,7 @@ def test_multi_array_1d_exposed_ufuncs_vs_numpy( def test_scalar_operations_vs_numpy(pk_ufunc, numpy_ufunc, numpy_dtype): data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] expected = numpy_ufunc(np.array(data, dtype=numpy_dtype), 1) - actual = pk_ufunc(pk.array(np.array(data, dtype=numpy_dtype)), 1) + actual = pk_ufunc(pk.asarray(np.array(data, dtype=numpy_dtype)), 1) assert_allclose(actual, expected) @@ -423,8 +423,8 @@ def test_np_matmul_2d_1d_vs_numpy(pk_ufunc, numpy_ufunc, numpy_dtype, test_dim): np2 = rng.random(N2).astype(numpy_dtype) expected = numpy_ufunc(np1, np2) - view1 = pk.array(np1) - view2 = pk.array(np2) + view1 = pk.asarray(np1) + view2 = pk.asarray(np2) actual = pk_ufunc(view1, view2) assert_allclose(actual, expected) @@ -454,8 +454,8 @@ def test_np_matmul_1d_2d_vs_numpy(pk_ufunc, numpy_ufunc, numpy_dtype, test_dim): np2 = rng.random((N2, M2)).astype(numpy_dtype) expected = numpy_ufunc(np1, np2) - view1 = pk.array(np1) - view2 = pk.array(np2) + view1 = pk.asarray(np1) + view2 = pk.asarray(np2) actual = pk_ufunc(view1, view2) assert_allclose(actual, expected) @@ -495,8 +495,8 @@ def test_np_matmul_fails(numpy_dtype, test_dim): np2 = rng.random((N2, M2)).astype(numpy_dtype) with pytest.raises(RuntimeError) as e_info: - view1 = pk.array(np1) - view2 = pk.array(np2) + view1 = pk.asarray(np1) + view2 = pk.asarray(np2) pk.np_matmul(view1, view2) # Should fail with 1d x 2d err_np_matmul = ( @@ -536,8 +536,8 @@ def test_multi_array_2d_exposed_ufuncs_vs_numpy(pk_ufunc, numpy_ufunc, numpy_dty np2 = rng.random((N, M)).astype(numpy_dtype) expected = numpy_ufunc(np1, np2) - view1 = pk.array(np1) - view2 = pk.array(np2) + view1 = pk.asarray(np1) + view2 = pk.asarray(np2) actual = pk_ufunc(view1, view2) assert_allclose(actual, expected) @@ -592,8 +592,8 @@ def test_broadcast_array_exposed_ufuncs_vs_numpy( expected = numpy_ufunc(np1, np2) - view1 = pk.array(np1) - view2 = pk.array(np2) if isinstance(np2, np.ndarray) else np2 + view1 = pk.asarray(np1) + view2 = pk.asarray(np2) if isinstance(np2, np.ndarray) else np2 actual = pk_ufunc(view1, view2) assert_allclose(expected, actual) @@ -643,8 +643,8 @@ def test_copyto_1d(pk_ufunc, numpy_ufunc, numpy_dtype): np2 = rng.random((N, M)).astype(numpy_dtype) numpy_ufunc(np1, np2) - view1 = pk.array(np1) - view2 = pk.array(np2) + view1 = pk.asarray(np1) + view2 = pk.asarray(np2) pk_ufunc(view1, view2) assert_allclose(np1, view1) @@ -705,8 +705,8 @@ def test_copyto_broadcast_2d(pk_ufunc, numpy_ufunc, numpy_dtype, test_dim): numpy_ufunc(np1, np2) - view1 = pk.array(np1) - view2 = pk.array(np2) if isinstance(np2, np.ndarray) else np2 + view1 = pk.asarray(np1) + view2 = pk.asarray(np2) if isinstance(np2, np.ndarray) else np2 pk_ufunc(view1, view2) assert_allclose(np1, view1) diff --git a/tests/test_views.py b/tests/test_views.py index 86c8d979..93ac65ed 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -97,9 +97,9 @@ def __init__(self, threads: int, i_1: int, i_2: int, i_3: int, i_4: int): cp_arr = cp.zeros((threads, 2)).astype(np.int32) list_arr = [np.array([0, 0], dtype=np.int32)] * threads - self.np_view: pk.View2D[int] = pk.array(np_arr) - self.cp_view: pk.View2D[int] = pk.array(cp_arr) - self.list_view: pk.View2D[int] = pk.array(list_arr) + self.np_view: pk.View2D[int] = pk.asarray(np_arr) + self.cp_view: pk.View2D[int] = pk._array(cp_arr) + self.list_view: pk.View2D[int] = pk.asarray(list_arr) @pk.workunit def v1d(self, tid: int) -> None: @@ -283,9 +283,9 @@ def test_arrays(self): cp_arr = cp.zeros((self.threads, 2)).astype(np.int32) list_arr = [np.array([0, 0], dtype=np.int32)] * self.threads - np_view = pk.array(np_arr) - cp_view = pk.array(cp_arr) - list_view = pk.array(list_arr) + np_view = pk.asarray(np_arr) + cp_view = pk._array(cp_arr) + list_view = pk.asarray(list_arr) pk.parallel_for( pk.RangePolicy(pk.OpenMP, 0, self.threads), addition_np, np_arr=np_view @@ -376,6 +376,41 @@ def test_asarray_consts_vs_numpy(const, np_dtype, pk_dtype): assert not "int" in pk_type_string +@pytest.mark.parametrize( + "arr", + [ + np.array([1, 2, 3], dtype=np.int32), + [1, 2, 3], + 7, + ], +) +def test_array_deprecated_alias_still_converts(arr): + with pytest.warns(DeprecationWarning, match="pk.array is deprecated"): + view = pk.array(arr) + assert_allclose(view, np.asarray(arr)) + + +@pytest.mark.parametrize( + "arr", + [ + np.array([3, 1, 4], dtype=np.int32), + [3, 1, 4], + ], +) +def test_private_array_conversion_matches_asarray(arr): + from_private = pk._array(arr) + from_public = pk.asarray(arr) + assert type(from_private) is type(from_public) + assert_allclose(from_private, from_public) + + +@pytest.mark.skipif(not HAS_CUDA, reason="CUDA/cupy not available") +def test_private_array_conversion_accepts_cupy_array(): + cp_arr = cp.array([3, 1, 4], dtype=cp.int32) + from_private = pk._array(cp_arr) + assert_allclose(from_private, cp.asnumpy(cp_arr)) + + @pytest.mark.parametrize( "pk_dtype, np_dtype", [ @@ -416,7 +451,7 @@ def test_result_type_supported(pk_dtype, pk_dtype2, expected_promo): @pytest.mark.parametrize( "pk_dtype, pk_dtype2", [ - (pk.array(np.array([0])), pk.uint16), + (pk.asarray(np.array([0])), pk.uint16), (pk.uint64, pk.int8), (pk.float32, pk.int64), ],