From 413eaa796d6dd8cc1fe8ef82f9429c79d0e0234f Mon Sep 17 00:00:00 2001 From: Egor Orachev Date: Mon, 28 Aug 2023 10:29:35 +0300 Subject: [PATCH] gh-218: impl types and array primitive in package --- LICENSE.md | 2 +- include/spla.h | 2 + python/LICENSE.md | 2 +- python/example.py | 6 +- python/pyspla/__init__.py | 17 ++- python/pyspla/__main__.py | 2 +- python/pyspla/array.py | 251 ++++++++++++++++++++++++++++++++++++++ python/pyspla/bridge.py | 29 +++-- python/pyspla/library.py | 2 +- python/pyspla/matrix.py | 2 +- python/pyspla/object.py | 23 +++- python/pyspla/op.py | 2 +- python/pyspla/scalar.py | 2 +- python/pyspla/schedule.py | 2 +- python/pyspla/type.py | 106 +++++++++++++++- python/pyspla/vector.py | 2 +- python/pyspla/version.py | 2 +- src/binding/c_array.cpp | 7 ++ 18 files changed, 432 insertions(+), 29 deletions(-) create mode 100644 python/pyspla/array.py diff --git a/LICENSE.md b/LICENSE.md index e156fb9c5..eaaca3283 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2021-2022 SparseLinearAlgebra +Copyright (c) 2021-2023 SparseLinearAlgebra Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/include/spla.h b/include/spla.h index 28f32b198..fde758505 100644 --- a/include/spla.h +++ b/include/spla.h @@ -217,12 +217,14 @@ SPLA_API spla_Status spla_Scalar_get_float(spla_Scalar s, float* value); /* Array container creation and manipulation */ SPLA_API spla_Status spla_Array_make(spla_Array* v, spla_uint n_values, spla_Type type); +SPLA_API spla_Status spla_Array_get_n_values(spla_Array a, spla_uint* values); SPLA_API spla_Status spla_Array_set_int(spla_Array a, spla_uint i, int value); SPLA_API spla_Status spla_Array_set_uint(spla_Array a, spla_uint i, unsigned int value); SPLA_API spla_Status spla_Array_set_float(spla_Array a, spla_uint i, float value); SPLA_API spla_Status spla_Array_get_int(spla_Array a, spla_uint i, int* value); SPLA_API spla_Status spla_Array_get_uint(spla_Array a, spla_uint i, unsigned int* value); SPLA_API spla_Status spla_Array_get_float(spla_Array a, spla_uint i, float* value); +SPLA_API spla_Status spla_Array_resize(spla_Array a, spla_uint n); SPLA_API spla_Status spla_Array_clear(spla_Array a); ////////////////////////////////////////////////////////////////////////////////////// diff --git a/python/LICENSE.md b/python/LICENSE.md index e156fb9c5..eaaca3283 100644 --- a/python/LICENSE.md +++ b/python/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2021-2022 SparseLinearAlgebra +Copyright (c) 2021-2023 SparseLinearAlgebra Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/python/example.py b/python/example.py index 3e0c7ace6..1c2f961fe 100644 --- a/python/example.py +++ b/python/example.py @@ -1,5 +1,3 @@ -import os - -os.environ["SPLA_PATH"] = "C:\\Users\\egoro\\Documents\\GitHub\\spla\\cmake-build-debug\\spla_x64.dll" - import pyspla + +print(pyspla.Array.generate(dtype=pyspla.FLOAT, shape=3, dist=[100, 300]).to_list()) diff --git a/python/pyspla/__init__.py b/python/pyspla/__init__.py index c4141f3af..3a97d56ba 100644 --- a/python/pyspla/__init__.py +++ b/python/pyspla/__init__.py @@ -128,7 +128,7 @@ on package exit. Library state finalized automatically. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License @@ -152,21 +152,34 @@ SOFTWARE. """ +from .bridge import * + +bridge.initialize() + from .library import * from .op import * from .object import * from .schedule import * from .type import * +from .array import * from .matrix import * from .vector import * from .scalar import * from .version import * -from .bridge import * + +if not bridge.is_docs(): + for t in BUILT_IN: + t._setup() __version__ = VERSIONS[-1] __all__ = [ + "Type", + "INT", + "UINT", + "FLOAT", "Object", + "Array", "Matrix", "Vector", "Scalar", diff --git a/python/pyspla/__main__.py b/python/pyspla/__main__.py index d9b65be3f..cb97ca3c1 100644 --- a/python/pyspla/__main__.py +++ b/python/pyspla/__main__.py @@ -2,7 +2,7 @@ Main entry point. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License diff --git a/python/pyspla/array.py b/python/pyspla/array.py new file mode 100644 index 000000000..ae31fd318 --- /dev/null +++ b/python/pyspla/array.py @@ -0,0 +1,251 @@ +""" +Wrapped native (spla C API) array primitive implementation. +""" + +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" + +__license__ = """ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import ctypes + +from .bridge import backend, check +from .type import Type, INT, UINT, FLOAT +from .object import Object +import random as rnd + + +class Array(Object): + """ + Generalized statically-typed dense linear array primitive. + + Attributes + ---------- + + - dtype : `Type` type of stored array elements + - shape : `2-tuple` shape of the array in form of two integers tuple (second dim is 1) + + Notes + ----- + + Array provides features for: + + - fill with values + - read-back by value + - source for vector construction + - source for matrix construction + + Array typical usage: + + - (1) Instantiate array primitive + - (2) Fill with values from your data + - (3) Use array to build entire vector or matrix + - (4) Read-back results of computations from vector or matrix + + Details + ------- + + Array class support all spla C API vector functions. + It provides bind functionality as well as new functions/methods for better python user experience. + + Array internally manages native optimized storage for values. + Reading and writing values into array is fast and has nearly no overhead. + """ + + __slots__ = ["_dtype"] + + def __init__(self, dtype=INT, shape=0, hnd=None, label=None): + """ + Creates new array of specified type and shape. + + :param dtype: Type parametrization of a storage + :param shape: Size of the array + :param hnd: Optional native handle to retain + :param label: Optional label to assign + """ + + super().__init__(None, None) + + assert dtype + assert shape + assert shape >= 0 + assert issubclass(dtype, Type) + + self._dtype = dtype + + if not hnd: + hnd = ctypes.c_void_p(0) + check(backend().spla_Array_make(ctypes.byref(hnd), ctypes.c_uint(shape), dtype._hnd)) + + super().__init__(label, hnd) + + @property + def dtype(self): + """ + Type used for storage parametrization of this container. + + :return: Type of stored values + """ + return self._dtype + + @property + def shape(self): + """ + 2-Tuple with shape of array where second value is always 1. + + :return: Size of array as a tuple + """ + + n_values = ctypes.c_uint(0) + check(backend().spla_Array_get_n_values(self._hnd, ctypes.byref(n_values))) + return int(n_values.value), 1 + + @property + def empty(self): + """ + Checks if array is empty (has 0-size) and returns true. + + :return: True if array is empty + """ + + return self.shape[0] == 0 + + def set(self, index, value): + """ + Set value at specified index. + + :param index: Index at which value to set + :param value: Value to set, must be convertible to destination type + :return: + """ + + check(self._dtype._array_set(self._hnd, ctypes.c_uint(index), self._dtype._c_type(value))) + + def get(self, index): + """ + Get value at specified index. + + :param index: Index at which to get value + :return: Value at specified index + """ + + value = self._dtype._c_type(0) + check(self._dtype._array_get(self._hnd, ctypes.c_uint(index), ctypes.byref(value))) + return self._dtype._to_python(value) + + def resize(self, shape=0): + """ + Resizes array to new size with desired num of values specified as shape. + + :param shape: New array capacity + :return: + """ + + check(backend().spla_Array_resize(self._hnd, ctypes.c_uint(shape))) + + def clear(self): + """ + Clears array removing all elements, so it has 0 values. + + :return: + """ + + check(backend().spla_Array_clear(self._hnd)) + + def to_list(self): + """ + Read array data as a python list of values. + + :return: List with values stored in the array + """ + + values = list() + value = self._dtype._c_type(0) + + for i in range(self.shape[0]): + check(self._dtype._array_get(self._hnd, ctypes.c_uint(i), ctypes.byref(value))) + values.append(self._dtype._to_python(value)) + + return values + + @classmethod + def from_list(cls, values, dtype=INT, shape=None): + """ + Creates new array of desired type and shape and fills its content with `values` data. + + :param values: List with values to fill array + :param dtype: Type of the array stored value + :param shape: Optional size of array, by default inferred from `values` + :return: Created array filled with values + """ + + if shape is None: + shape = len(values) + assert shape >= len(values) + array = Array(dtype=dtype, shape=shape) + for i, v in enumerate(values): + array.set(i, v) + return array + + @classmethod + def generate(cls, dtype=INT, shape=0, seed=None, dist=(0, 1)): + """ + Creates new array of desired type and shape and fills its content + with random values, generated using specified distribution. + + :param dtype: Type of values array will have + :param shape: Size of the array (number of values) + :param seed: Optional seed to randomize generator + :param dist: Optional distribution for uniform generation of values + :return: Created array filled with values + """ + + array = Array(dtype=dtype, shape=shape) + + if seed is not None: + rnd.seed(seed) + + if dtype is INT: + for i in range(shape): + array.set(i, rnd.randint(dist[0], dist[1])) + elif dtype is UINT: + for i in range(shape): + array.set(i, rnd.randint(dist[0], dist[1])) + elif dtype is FLOAT: + for i in range(shape): + array.set(i, rnd.uniform(dist[0], dist[1])) + + return array + + def __str__(self): + return str(self.to_list()) + + def __iter__(self): + return iter(self.to_list()) + + def __setitem__(self, key, value): + assert isinstance(key, int) + self.set(key, value) + + def __getitem__(self, item): + assert isinstance(item, int) + return self.get(item) diff --git a/python/pyspla/bridge.py b/python/pyspla/bridge.py index e5fbfde36..157f1eb15 100644 --- a/python/pyspla/bridge.py +++ b/python/pyspla/bridge.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) raw functions access. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License @@ -27,8 +27,9 @@ """ __all__ = [ - "_spla", - "_callback_t" + "backend", + "check", + "is_docs" ] import os @@ -51,6 +52,7 @@ _p_object_t = None _callback_t = None _default_callback = None +_is_docs = False class SplaError(Exception): @@ -300,21 +302,25 @@ def load_library(lib_path): _spla.spla_Scalar_get_float.argtypes = [_object_t, _p_float] _spla.spla_Array_make.restype = _status_t + _spla.spla_Array_get_n_values.restype = _status_t _spla.spla_Array_set_int.restype = _status_t _spla.spla_Array_set_uint.restype = _status_t _spla.spla_Array_set_float.restype = _status_t _spla.spla_Array_get_int.restype = _status_t _spla.spla_Array_get_uint.restype = _status_t _spla.spla_Array_get_float.restype = _status_t + _spla.spla_Array_resize.restype = _status_t _spla.spla_Array_clear.restype = _status_t _spla.spla_Array_make.argtypes = [_p_object_t, _uint, _object_t] + _spla.spla_Array_get_n_values.argtypes = [_object_t, _p_uint] _spla.spla_Array_set_int.argtypes = [_object_t, _uint, _int] _spla.spla_Array_set_uint.argtypes = [_object_t, _uint, _uint] _spla.spla_Array_set_float.argtypes = [_object_t, _uint, _float] _spla.spla_Array_get_int.argtypes = [_object_t, _uint, _p_int] _spla.spla_Array_get_uint.argtypes = [_object_t, _uint, _p_uint] _spla.spla_Array_get_float.argtypes = [_object_t, _uint, _p_float] + _spla.spla_Array_resize.argtypes = [_object_t, _uint] _spla.spla_Array_clear.argtypes = [_object_t] _spla.spla_Vector_make.restype = _status_t @@ -383,7 +389,7 @@ def load_library(lib_path): def default_callback(status, msg, file, function, line, user_data): decoded_msg = msg.decode("utf-8") decoded_file = file.decode("utf-8") - print(f"PySpla: [{decoded_file}:{line}] {_status_mapping[status]}: {decoded_msg}") + print(f"pyspla: [{decoded_file}:{line}] {_status_mapping[status]}: {decoded_msg}") def finalize(): @@ -392,6 +398,7 @@ def finalize(): def initialize(): + global _is_docs global _spla global _spla_path global _callback_t @@ -400,6 +407,7 @@ def initialize(): try: # If generating docs, no lib init required if os.environ["SPLA_DOCS"]: + _is_docs = True return except KeyError: pass @@ -423,8 +431,7 @@ def initialize(): try: # If debug enable in ENV, setup default callback for messages on init if int(os.environ["SPLA_DEBUG"]): - _spla.spla_Library_set_message_callback( - _default_callback, ctypes.c_void_p(0)) + _spla.spla_Library_set_message_callback(_default_callback, ctypes.c_void_p(0)) except KeyError: pass @@ -436,5 +443,11 @@ def check(status): raise _status_mapping[status] -# Initialize bridge on import -initialize() +def is_docs(): + global _is_docs + return _is_docs + + +def backend(): + global _spla + return _spla diff --git a/python/pyspla/library.py b/python/pyspla/library.py index 9112fb0b6..533699acf 100644 --- a/python/pyspla/library.py +++ b/python/pyspla/library.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) library implementation. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License diff --git a/python/pyspla/matrix.py b/python/pyspla/matrix.py index 740884d7e..8f2db47f4 100644 --- a/python/pyspla/matrix.py +++ b/python/pyspla/matrix.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) matrix primitive implementation. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License diff --git a/python/pyspla/object.py b/python/pyspla/object.py index 11768a012..27b38ea0b 100644 --- a/python/pyspla/object.py +++ b/python/pyspla/object.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) object primitive implementation. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License @@ -28,6 +28,8 @@ import ctypes +from .bridge import backend, check + class Object: """ @@ -37,7 +39,7 @@ class Object: ---------- - label : `str` user provided text label for object for debugging - - hnd : `ctypes.p_void` hnd to native object in spla C API + - hnd : `ctypes.c_void_p` hnd to native object in spla C API Details ------- @@ -49,5 +51,18 @@ class Object: of native spla C/C++ instances, created inside imported native shared spla (.dll/.so/.dylib) library. """ - def __init__(self): - pass + __slots__ = ["_hnd", "_label"] + + def __init__(self, label, hnd): + self._hnd = hnd + self._label = label + + def __del__(self): + if self._hnd: + check(backend().spla_Object_unref(self._hnd)) + + def set_label(self, label): + self._label = label + + def get_label(self): + return self._label diff --git a/python/pyspla/op.py b/python/pyspla/op.py index e4069198f..72b3c06b9 100644 --- a/python/pyspla/op.py +++ b/python/pyspla/op.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) op implementation. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License diff --git a/python/pyspla/scalar.py b/python/pyspla/scalar.py index 33d3a3ecf..2bb8bcfdb 100644 --- a/python/pyspla/scalar.py +++ b/python/pyspla/scalar.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) scalar primitive implementation. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License diff --git a/python/pyspla/schedule.py b/python/pyspla/schedule.py index 3962e01e6..45ffe2730 100644 --- a/python/pyspla/schedule.py +++ b/python/pyspla/schedule.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) schedule implementation. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License diff --git a/python/pyspla/type.py b/python/pyspla/type.py index eb4334d23..8ecf0fff6 100644 --- a/python/pyspla/type.py +++ b/python/pyspla/type.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) type support implementation. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License @@ -25,3 +25,107 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + +import ctypes + +from .bridge import backend + +__all__ = [ + 'Type', + 'INT', + 'UINT', + 'FLOAT', + 'BUILT_IN' +] + + +class Type: + """Spla base Type for storage parametrization.""" + + _c_type = None + _c_type_p = None + _code = '' + _scalar_get = None + _scalar_set = None + _array_get = None + _array_set = None + _vector_get = None + _vector_set = None + _matrix_get = None + _matrix_set = None + _hnd = None + + +class INT(Type): + """Spla integral INT-32 type.""" + + _c_type = ctypes.c_int + _c_type_p = ctypes.POINTER(ctypes.c_int) + _code = 'I' + + @classmethod + def _setup(cls): + cls._scalar_get = backend().spla_Scalar_get_int + cls._scalar_set = backend().spla_Scalar_set_int + cls._array_get = backend().spla_Array_get_int + cls._array_set = backend().spla_Array_set_int + cls._vector_get = backend().spla_Vector_get_int + cls._vector_set = backend().spla_Vector_set_int + cls._matrix_get = backend().spla_Matrix_get_int + cls._matrix_set = backend().spla_Matrix_set_int + cls._hnd = backend().spla_Type_int() + + @classmethod + def _to_python(cls, value): + return int(value.value) + + +class UINT(Type): + """Spla integral UINT-32 type.""" + + _c_type = ctypes.c_uint + _c_type_p = ctypes.POINTER(ctypes.c_uint) + _code = 'U' + + @classmethod + def _setup(cls): + cls._scalar_get = backend().spla_Scalar_get_uint + cls._scalar_set = backend().spla_Scalar_set_uint + cls._array_get = backend().spla_Array_get_uint + cls._array_set = backend().spla_Array_set_uint + cls._vector_get = backend().spla_Vector_get_uint + cls._vector_set = backend().spla_Vector_set_uint + cls._matrix_get = backend().spla_Matrix_get_uint + cls._matrix_set = backend().spla_Matrix_set_uint + cls._hnd = backend().spla_Type_uint() + + @classmethod + def _to_python(cls, value): + return int(value.value) + + +class FLOAT(Type): + """Spla floating-point FLOAT-32 type.""" + + _c_type = ctypes.c_float + _c_type_p = ctypes.POINTER(ctypes.c_float) + _code = 'F' + + @classmethod + def _setup(cls): + cls._scalar_get = backend().spla_Scalar_get_float + cls._scalar_set = backend().spla_Scalar_set_float + cls._array_get = backend().spla_Array_get_float + cls._array_set = backend().spla_Array_set_float + cls._vector_get = backend().spla_Vector_get_float + cls._vector_set = backend().spla_Vector_set_float + cls._matrix_get = backend().spla_Matrix_get_float + cls._matrix_set = backend().spla_Matrix_set_float + cls._hnd = backend().spla_Type_float() + + @classmethod + def _to_python(cls, value): + return float(value.value) + + +BUILT_IN = [INT, UINT, FLOAT] diff --git a/python/pyspla/vector.py b/python/pyspla/vector.py index 46d0b3e17..49f290e7d 100644 --- a/python/pyspla/vector.py +++ b/python/pyspla/vector.py @@ -2,7 +2,7 @@ Wrapped native (spla C API) vector primitive implementation. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License diff --git a/python/pyspla/version.py b/python/pyspla/version.py index 59aa67f87..929c5e3d2 100644 --- a/python/pyspla/version.py +++ b/python/pyspla/version.py @@ -2,7 +2,7 @@ Package version info. """ -__copyright__ = "Copyright (c) 2021-2022 SparseLinearAlgebra" +__copyright__ = "Copyright (c) 2021-2023 SparseLinearAlgebra" __license__ = """ MIT License diff --git a/src/binding/c_array.cpp b/src/binding/c_array.cpp index 960cae882..8ee1be878 100644 --- a/src/binding/c_array.cpp +++ b/src/binding/c_array.cpp @@ -32,6 +32,10 @@ spla_Status spla_Array_make(spla_Array* v, spla_uint n_values, spla_Type type) { *v = as_ptr(array.release()); return SPLA_STATUS_OK; } +spla_Status spla_Array_get_n_values(spla_Array a, spla_uint* values) { + *values = as_ptr(a)->get_n_values(); + return SPLA_STATUS_OK; +} spla_Status spla_Array_set_int(spla_Array a, spla_uint i, int value) { return to_c_status(as_ptr(a)->set_int(i, value)); } @@ -50,6 +54,9 @@ spla_Status spla_Array_get_uint(spla_Array a, spla_uint i, unsigned int* value) spla_Status spla_Array_get_float(spla_Array a, spla_uint i, float* value) { return to_c_status(as_ptr(a)->get_float(i, *value)); } +spla_Status spla_Array_resize(spla_Array a, spla_uint n) { + return to_c_status(as_ptr(a)->resize(n)); +} spla_Status spla_Array_clear(spla_Array a) { return to_c_status(as_ptr(a)->clear()); } \ No newline at end of file