diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rvec.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rvec.py index 7e5f8c42e1ad6..086f13ac0ea2b 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rvec.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rvec.py @@ -134,6 +134,8 @@ def _get_cpp_type_from_numpy_type(dtype): cpptypes = { + "i1": "signed char", + "u1": "unsigned char", "i2": "Short_t", "u2": "UShort_t", "i4": "int", @@ -161,7 +163,8 @@ def _AsRVec(arr): This function returns an RVec which adopts the memory of the given PyObject. The RVec takes the data pointer and the size from the array interface dictionary. - Note that for arrays of strings, the input strings are copied into the RVec. + Note that for arrays of strings and int8, the input data is copied into the RVec + rather than adopted. """ import math @@ -183,7 +186,7 @@ def _AsRVec(arr): typestr = interface["typestr"] dtype = typestr[1:] - # Construct an RVec of strings + # Construct an RVec of strings by copying the input array if dtype == "O" or dtype.startswith("U"): underlying_object_types = {type(elem) for elem in arr} if len(underlying_object_types) > 1: @@ -198,6 +201,11 @@ def _AsRVec(arr): else: raise TypeError("Cannot create an RVec from a numpy array of data type object.") + # Construct an RVec of int8 by copying the input array + # RVec does not expose the ptr+size contructor + if dtype == "i1": + return ROOT.VecOps.RVec["signed char"](arr) + if len(typestr) != 3: raise RuntimeError( "Object not convertible: __array_interface__['typestr'] returned '" diff --git a/bindings/pyroot/pythonizations/test/rvec_asrvec.py b/bindings/pyroot/pythonizations/test/rvec_asrvec.py index e1a1d4fbd3e28..a3ebb2fe16fdc 100644 --- a/bindings/pyroot/pythonizations/test/rvec_asrvec.py +++ b/bindings/pyroot/pythonizations/test/rvec_asrvec.py @@ -27,13 +27,20 @@ class AsRVec(unittest.TestCase): """ # Helpers - dtypes = ["int16", "int32", "int64", "uint16", "uint32", "uint64", "float32", "float64", "bool"] + dtypes = ["int8", "uint8", "int16", "int32", "int64", "uint16", "uint32", "uint64", "float32", "float64", "bool"] - def check_memory_adoption(self, root_obj, np_obj): - np_obj[0] = get_maximum_for_dtype(np_obj.dtype) - np_obj[1] = get_minimum_for_dtype(np_obj.dtype) - self.assertEqual(root_obj[0], np_obj[0]) - self.assertEqual(root_obj[1], np_obj[1]) + def check_memory_adoption(self, root_obj, np_obj, copy=False): + if not copy: + np_obj[0] = get_maximum_for_dtype(np_obj.dtype) + np_obj[1] = get_minimum_for_dtype(np_obj.dtype) + for i in range(2): + root_val = root_obj[i] + # RVec elements are returned as Python strings (e.g. '\x7f' instead of 127), + # we need to convert them back to integers for comparison + if isinstance(root_val, str): + root_val = ord(root_val) + + self.assertEqual(root_val, np_obj[i]) def check_size(self, expected_size, obj): self.assertEqual(expected_size, obj.size()) @@ -44,9 +51,15 @@ def test_dtypes(self): Test adoption of numpy arrays with different data types """ for dtype in self.dtypes: - np_obj = np.empty(2, dtype=dtype) - root_obj = ROOT.VecOps.AsRVec(np_obj) - self.check_memory_adoption(root_obj, np_obj) + if dtype == "int8": + # For int8 we use explicit small values to avoid platform-dependent signed char representation issues + np_obj = np.array([42, 35], dtype="int8") + root_obj = ROOT.VecOps.AsRVec(np_obj) + self.check_memory_adoption(root_obj, np_obj, copy=True) + else: + np_obj = np.empty(2, dtype=dtype) + root_obj = ROOT.VecOps.AsRVec(np_obj) + self.check_memory_adoption(root_obj, np_obj) self.check_size(2, root_obj) def test_object_dtype_strings(self):