diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ca8547d..83a4a01a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s ### Changed #### Conduit - Changed Conduit memory handler callbacks from function pointers to `std::function` objects, allowing users more flexibility in dealing with different memory spaces. +- Added element stride helper methods to the DataType class. #### Relay - Ported relay and blueprint zfp support to use zfp 1.0 api. Added extra meta data to zfparray blueprint protocol to support roundtrip wrapping and unwrapping with zfp 1.0 api. diff --git a/src/libs/conduit/c/conduit_datatype.h b/src/libs/conduit/c/conduit_datatype.h index 5c68c4428..51bce1e32 100644 --- a/src/libs/conduit/c/conduit_datatype.h +++ b/src/libs/conduit/c/conduit_datatype.h @@ -91,6 +91,10 @@ CONDUIT_API int conduit_datatype_is_little_endian(const conduit_datatype *cdatat CONDUIT_API int conduit_datatype_is_big_endian(const conduit_datatype *cdatatype); CONDUIT_API int conduit_datatype_endianness_matches_machine(const conduit_datatype *cdatatype); +CONDUIT_API conduit_index_t conduit_datatype_element_stride(const conduit_datatype *cdatatype); +CONDUIT_API int conduit_datatype_is_stride_element_aligned(const conduit_datatype *cdatatype); +CONDUIT_API int conduit_datatype_is_stride_aligned(const conduit_datatype *cdatatype, conduit_index_t nbytes); + #ifdef __cplusplus } #endif diff --git a/src/libs/conduit/c/conduit_datatype_c.cpp b/src/libs/conduit/c/conduit_datatype_c.cpp index dcca9a03b..32f30d496 100644 --- a/src/libs/conduit/c/conduit_datatype_c.cpp +++ b/src/libs/conduit/c/conduit_datatype_c.cpp @@ -251,6 +251,21 @@ int conduit_datatype_endianness_matches_machine(const conduit_datatype *cdatatyp return cpp_datatype_ref(cdatatype).endianness_matches_machine() ? 1 : 0; } +conduit_index_t conduit_datatype_element_stride(const conduit_datatype *cdatatype) +{ + return cpp_datatype_ref(cdatatype).element_stride(); +} + +int conduit_datatype_is_stride_element_aligned(const conduit_datatype *cdatatype) +{ + return cpp_datatype_ref(cdatatype).is_stride_element_aligned() ? 1 : 0; +} + +int conduit_datatype_is_stride_aligned(const conduit_datatype *cdatatype, conduit_index_t nbytes) +{ + return cpp_datatype_ref(cdatatype).is_stride_aligned(nbytes) ? 1 : 0; +} + } //----------------------------------------------------------------------------- // -- end extern C diff --git a/src/libs/conduit/conduit_data_type.cpp b/src/libs/conduit/conduit_data_type.cpp index af6a71784..2e1d28fe2 100644 --- a/src/libs/conduit/conduit_data_type.cpp +++ b/src/libs/conduit/conduit_data_type.cpp @@ -1050,6 +1050,53 @@ DataType::element_index(conduit::index_t idx) const return m_offset + m_stride * idx; } +//---------------------------------------------------------------------------// +conduit::index_t +DataType::element_stride() const +{ + // Check for zero to avoid division by zero + if(m_ele_bytes == 0) + { + return 0; + } + + return m_stride / m_ele_bytes; +} + +//---------------------------------------------------------------------------// +bool +DataType::is_stride_element_aligned() const +{ + // If element_bytes is zero, we can't determine alignment + if(m_ele_bytes == 0) + { + return false; + } + + // Test if stride is a multiple of element_bytes + return (m_stride % m_ele_bytes) == 0; +} + +//---------------------------------------------------------------------------// +bool +DataType::is_stride_aligned(conduit::index_t nbytes) const +{ + // If nbytes is zero, we can't determine alignment + if(nbytes == 0) + { + return false; + } + + // For zero stride (empty case), consider it aligned with everything + if(m_stride == 0) + { + return true; + } + + // Test if stride is a multiple of nbytes + return (m_stride % nbytes) == 0; +} + //----------------------------------------------------------------------------- // TypeID to string and string to TypeId //----------------------------------------------------------------------------- diff --git a/src/libs/conduit/conduit_data_type.hpp b/src/libs/conduit/conduit_data_type.hpp index 1b3bd403d..9f32379f5 100644 --- a/src/libs/conduit/conduit_data_type.hpp +++ b/src/libs/conduit/conduit_data_type.hpp @@ -399,6 +399,13 @@ class CONDUIT_API DataType conduit::index_t element_bytes() const { return m_ele_bytes;} conduit::index_t endianness() const { return m_endianness;} conduit::index_t element_index(conduit::index_t idx) const; + + /// Returns the stride in elements (stride in bytes / element_bytes) + conduit::index_t element_stride() const; + /// Tests if stride is aligned with element boundaries (stride % element_bytes == 0) + bool is_stride_element_aligned() const; + /// Tests if stride is a multiple of given bytes + bool is_stride_aligned(conduit::index_t nbytes) const; /// strided bytes = stride() * (number_of_elements() -1) + element_bytes() conduit::index_t strided_bytes() const; diff --git a/src/libs/conduit/fortran/conduit_fortran.F90 b/src/libs/conduit/fortran/conduit_fortran.F90 index db1411a2d..c7c66e541 100644 --- a/src/libs/conduit/fortran/conduit_fortran.F90 +++ b/src/libs/conduit/fortran/conduit_fortran.F90 @@ -1386,6 +1386,15 @@ pure function conduit_datatype_element_bytes(cdatatype) result(res) & integer(kind(F_CONDUIT_INDEX_ID)) :: res end function conduit_datatype_element_bytes + !-------------------------------------------------------------------------- + pure function conduit_datatype_element_stride(cdatatype) result(res) & + bind(C, name="conduit_datatype_element_stride") + use iso_c_binding + implicit none + type(C_PTR), value, intent(IN) :: cdatatype + integer(kind(F_CONDUIT_INDEX_ID)) :: res + end function conduit_datatype_element_stride + !-------------------------------------------------------------------------- pure function conduit_datatype_endianness(cdatatype) result(res) & bind(C, name="conduit_datatype_endianness") @@ -1705,6 +1714,25 @@ pure function c_conduit_datatype_endianness_matches_machine(cdatatype) result(re type(C_PTR), value, intent(IN) :: cdatatype integer(C_INT) :: res end function c_conduit_datatype_endianness_matches_machine + + !-------------------------------------------------------------------------- + pure function c_conduit_datatype_is_stride_element_aligned(cdatatype) result(res) & + bind(C, name="conduit_datatype_is_stride_element_aligned") + use iso_c_binding + implicit none + type(C_PTR), value, intent(IN) :: cdatatype + integer(C_INT) :: res + end function c_conduit_datatype_is_stride_element_aligned + + !-------------------------------------------------------------------------- + pure function c_conduit_datatype_is_stride_aligned(cdatatype, nbytes) result(res) & + bind(C, name="conduit_datatype_is_stride_aligned") + use iso_c_binding + implicit none + type(C_PTR), value, intent(IN) :: cdatatype + integer(kind(F_CONDUIT_INDEX_ID)), value, intent(IN) :: nbytes + integer(C_INT) :: res + end function c_conduit_datatype_is_stride_aligned !-------------------------------------------------------------------------- diff --git a/src/libs/conduit/python/conduit_python.cpp b/src/libs/conduit/python/conduit_python.cpp index b9564ca13..1dd9d63f8 100644 --- a/src/libs/conduit/python/conduit_python.cpp +++ b/src/libs/conduit/python/conduit_python.cpp @@ -2656,6 +2656,49 @@ PyConduit_DataType_float64_id(PyObject *) // unused return PyLong_FromSsize_t(DataType::FLOAT64_ID); } +//----------------------------------------------------------------------------- +static PyObject * +PyConduit_DataType_element_stride(PyConduit_DataType *self) +{ + return PyLong_FromSsize_t(self->dtype.element_stride()); +} + +//----------------------------------------------------------------------------- +static PyObject * +PyConduit_DataType_is_stride_element_aligned(PyConduit_DataType *self) +{ + if(self->dtype.is_stride_element_aligned()) + { + Py_RETURN_TRUE; + } + else + { + Py_RETURN_FALSE; + } +} + +//----------------------------------------------------------------------------- +static PyObject * +PyConduit_DataType_is_stride_aligned(PyConduit_DataType *self, + PyObject *args) +{ + Py_ssize_t nbytes; + if (!PyArg_ParseTuple(args, "n", &nbytes)) + { + PyErr_SetString(PyExc_TypeError, + "nbytes must be an integer"); + return NULL; + } + + if(self->dtype.is_stride_aligned((index_t)nbytes)) + { + Py_RETURN_TRUE; + } + else + { + Py_RETURN_FALSE; + } +} //----------------------------------------------------------------------------// // DataType methods table @@ -2726,6 +2769,21 @@ static PyMethodDef PyConduit_DataType_METHODS[] = { METH_NOARGS, "Returns the number of bytes per element property of this DataType"}, //-----------------------------------------------------------------------// + {"element_stride", + (PyCFunction)PyConduit_DataType_element_stride, + METH_NOARGS, + "Returns the element stride (stride / element_bytes) property of this DataType"}, + //-----------------------------------------------------------------------// + {"is_stride_element_aligned", + (PyCFunction)PyConduit_DataType_is_stride_element_aligned, + METH_NOARGS, + "Returns whether the stride is aligned with element boundaries (stride % element_bytes == 0)"}, + //-----------------------------------------------------------------------// + {"is_stride_aligned", + (PyCFunction)PyConduit_DataType_is_stride_aligned, + METH_VARARGS, + "Returns whether the stride is aligned with the given number of bytes (stride % nbytes == 0)"}, + //-----------------------------------------------------------------------// {"endianness", (PyCFunction)PyConduit_DataType_endianness, METH_NOARGS, diff --git a/src/tests/conduit/c/t_c_conduit_datatype.cpp b/src/tests/conduit/c/t_c_conduit_datatype.cpp index 3ea44bb23..b9bcb128d 100644 --- a/src/tests/conduit/c/t_c_conduit_datatype.cpp +++ b/src/tests/conduit/c/t_c_conduit_datatype.cpp @@ -26,3 +26,35 @@ TEST(c_conduit_datatype, sizeof_index_t) } +//----------------------------------------------------------------------------- +TEST(c_conduit_datatype, stride_methods) +{ + conduit_node *n = conduit_node_create(); + + // Create a simple array with stride = element_bytes + conduit_float64 arr_vals[] = {10.0, 20.0, 30.0, 40.0, 50.0}; + conduit_node_set_external_float64_ptr(n, arr_vals, 5); + + // Get the datatype + const conduit_datatype *dt = conduit_node_dtype(n); + + // Check stride-related properties + conduit_index_t stride = conduit_datatype_stride(dt); + conduit_index_t element_bytes = conduit_datatype_element_bytes(dt); + conduit_index_t element_stride = conduit_datatype_element_stride(dt); + + // Verify that stride / element_bytes = element_stride + EXPECT_EQ(stride / element_bytes, element_stride); + + // Since this is an array with standard layout, stride should be aligned with element bytes + EXPECT_EQ(conduit_datatype_is_stride_element_aligned(dt), 1); + + // Test stride aligned with various byte sizes + EXPECT_EQ(conduit_datatype_is_stride_aligned(dt, 8), 1); // Stride should be aligned with 8 bytes + EXPECT_EQ(conduit_datatype_is_stride_aligned(dt, 4), 1); // Stride should be aligned with 4 bytes + EXPECT_EQ(conduit_datatype_is_stride_aligned(dt, 2), 1); // Stride should be aligned with 2 bytes + + // Clean up + conduit_node_destroy(n); +} + diff --git a/src/tests/conduit/fortran/t_f_conduit_node_datatype.f90 b/src/tests/conduit/fortran/t_f_conduit_node_datatype.f90 index 46efdbd39..533939cd3 100644 --- a/src/tests/conduit/fortran/t_f_conduit_node_datatype.f90 +++ b/src/tests/conduit/fortran/t_f_conduit_node_datatype.f90 @@ -144,6 +144,14 @@ subroutine t_node_datatype_ids call assert_true( conduit_datatype_element_bytes(dataType) == 8) call assert_true( conduit_datatype_stride(dataType) == 8) call assert_true( conduit_datatype_offset(dataType) == 0) + + ! Test the new element stride methods + call assert_true( conduit_datatype_element_stride(dataType) == 1) ! 8/8 = 1 + call assert_true( c_conduit_datatype_is_stride_element_aligned(dataType) == 1) ! 8 % 8 = 0 + call assert_true( c_conduit_datatype_is_stride_aligned(dataType, 8) == 1) ! 8 % 8 = 0 + call assert_true( c_conduit_datatype_is_stride_aligned(dataType, 4) == 1) ! 8 % 4 = 0 + call assert_true( c_conduit_datatype_is_stride_aligned(dataType, 2) == 1) ! 8 % 2 = 0 + call assert_true( c_conduit_datatype_is_number(dataType) == 1) call assert_true( c_conduit_datatype_is_integer(dataType) == 0) call assert_true( c_conduit_datatype_is_signed_integer(dataType) == 0) diff --git a/src/tests/conduit/python/t_python_conduit_datatype.py b/src/tests/conduit/python/t_python_conduit_datatype.py index 87ba06b68..47f16c9c5 100644 --- a/src/tests/conduit/python/t_python_conduit_datatype.py +++ b/src/tests/conduit/python/t_python_conduit_datatype.py @@ -257,7 +257,57 @@ def test_to_string_and_friends(self): self.assertEqual(d.to_string("yaml"),d.to_yaml()) self.assertEqual(d.to_string("json"),d.to_json()) - + def test_stride_methods(self): + # Regular and aligned stride case + dt = DataType() + dt.set(dtype_id = DataType.name_to_id("uint32"), + num_elements = 10, + offset = 0, + stride = 8, + element_bytes = 4) + + # Element stride should be stride / element_bytes (8 / 4 = 2) + self.assertEqual(dt.element_stride(), 2) + # Stride is aligned with element boundaries (8 % 4 = 0) + self.assertTrue(dt.is_stride_element_aligned()) + # Stride is aligned with 2-byte boundary (8 % 2 = 0) + self.assertTrue(dt.is_stride_aligned(2)) + # Stride is aligned with 4-byte boundary (8 % 4 = 0) + self.assertTrue(dt.is_stride_aligned(4)) + # Stride is not aligned with 3-byte boundary (8 % 3 != 0) + self.assertFalse(dt.is_stride_aligned(3)) + + # Non-aligned stride case + dt.set_stride(6) + # Element stride should be integer division (6 / 4 = 1) + self.assertEqual(dt.element_stride(), 1) + # Stride is not aligned with element boundaries (6 % 4 != 0) + self.assertFalse(dt.is_stride_element_aligned()) + # Stride is aligned with 2-byte boundary (6 % 2 = 0) + self.assertTrue(dt.is_stride_aligned(2)) + # Stride is aligned with 3-byte boundary (6 % 3 = 0) + self.assertTrue(dt.is_stride_aligned(3)) + # Stride is not aligned with 4-byte boundary (6 % 4 != 0) + self.assertFalse(dt.is_stride_aligned(4)) + + # Special case - zero stride + dt.set_stride(0) + # Element stride should be 0 for zero stride (0 / 4 = 0) + self.assertEqual(dt.element_stride(), 0) + # Zero stride is considered element aligned + self.assertTrue(dt.is_stride_element_aligned()) + # Zero stride is aligned with any byte boundary + self.assertTrue(dt.is_stride_aligned(4)) + self.assertTrue(dt.is_stride_aligned(3)) + self.assertTrue(dt.is_stride_aligned(2)) + + # Special case - zero element_bytes + dt.set_stride(8) + dt.set_element_bytes(0) + # Element stride is currently defined to return 0 for zero element_bytes case + self.assertEqual(dt.element_stride(), 0) + # Element alignment is currently defined to return true for zero element_bytes + self.assertTrue(dt.is_stride_element_aligned()) if __name__ == '__main__': diff --git a/src/tests/conduit/t_conduit_datatype_tests.cpp b/src/tests/conduit/t_conduit_datatype_tests.cpp index 91a568b71..5b90cbea8 100644 --- a/src/tests/conduit/t_conduit_datatype_tests.cpp +++ b/src/tests/conduit/t_conduit_datatype_tests.cpp @@ -654,6 +654,70 @@ TEST(dtype_tests,dtype_endianness_checks) EXPECT_FALSE(dt.endianness_matches_machine()); } +//----------------------------------------------------------------------------- +TEST(dtype_tests,dtype_stride_checks) +{ + // Test for element stride calculation and alignment checks + DataType dt; + + // Standard case: stride equals element_bytes (compact data) + dt.set(DataType::FLOAT64_ID, + 10, // 10 elements + 0, // 0 offset + sizeof(float64), // stride = 8 bytes + sizeof(float64), // element_bytes = 8 bytes + Endianness::DEFAULT_ID); + + EXPECT_EQ(dt.element_stride(), 1); + EXPECT_TRUE(dt.is_stride_element_aligned()); + EXPECT_TRUE(dt.is_stride_aligned(4)); + EXPECT_TRUE(dt.is_stride_aligned(2)); + EXPECT_TRUE(dt.is_stride_aligned(1)); + + // Strided case: stride is multiple of element_bytes + dt.set(DataType::FLOAT64_ID, + 10, // 10 elements + 0, // 0 offset + sizeof(float64)*2, // stride = 16 bytes (2 elements) + sizeof(float64), // element_bytes = 8 bytes + Endianness::DEFAULT_ID); + + EXPECT_EQ(dt.element_stride(), 2); + EXPECT_TRUE(dt.is_stride_element_aligned()); + EXPECT_TRUE(dt.is_stride_aligned(8)); + EXPECT_TRUE(dt.is_stride_aligned(4)); + EXPECT_TRUE(dt.is_stride_aligned(2)); + + // Unaligned case: stride is not a multiple of element_bytes + dt.set(DataType::FLOAT64_ID, + 10, // 10 elements + 0, // 0 offset + sizeof(float64) + 1, // stride = 9 bytes (8 + 1) + sizeof(float64), // element_bytes = 8 bytes + Endianness::DEFAULT_ID); + + // Since 9/8 = 1.125, the element_stride will be 1 (integer division) + // but it's not properly aligned + EXPECT_EQ(dt.element_stride(), 1); + EXPECT_FALSE(dt.is_stride_element_aligned()); + EXPECT_FALSE(dt.is_stride_aligned(8)); + EXPECT_FALSE(dt.is_stride_aligned(4)); + EXPECT_FALSE(dt.is_stride_aligned(2)); + EXPECT_TRUE(dt.is_stride_aligned(1)); + + // Zero element_bytes case (should handle gracefully) + dt.set(DataType::EMPTY_ID, + 0, // 0 elements + 0, // 0 offset + 0, // stride = 0 bytes + 0, // element_bytes = 0 bytes + Endianness::DEFAULT_ID); + + EXPECT_EQ(dt.element_stride(), 0); + EXPECT_FALSE(dt.is_stride_element_aligned()); + EXPECT_TRUE(dt.is_stride_aligned(8)); +} + //----------------------------------------------------------------------------- TEST(dtype_tests,dtype_to_string_simple_checks)