From 961bc0d93938019e0b8ad1ab5e8d284dcacb71e9 Mon Sep 17 00:00:00 2001 From: Susanne Claus Date: Fri, 5 Dec 2025 12:51:22 +0100 Subject: [PATCH 1/6] Implement a generic Numba intrinsic factory in utils.py: create_voidptr_to_dtype_ptr_caster to bitcast void* to typed pointer. Provide convenience casters for common primitives: float64, float32, int32, int64. Add unit tests in test_numba_custom_data.py exercising the new casters: Tests cover basic float/double and int pointer casting and multi-parameter usage. --- ffcx/codegeneration/utils.py | 50 ++++++++++ test/test_numba_custom_data.py | 163 +++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 test/test_numba_custom_data.py diff --git a/ffcx/codegeneration/utils.py b/ffcx/codegeneration/utils.py index ec7d72358..406c624f0 100644 --- a/ffcx/codegeneration/utils.py +++ b/ffcx/codegeneration/utils.py @@ -159,3 +159,53 @@ def codegen(context, builder, signature, args): sig = numba.types.voidptr(arr) return sig, codegen + + def create_voidptr_to_dtype_ptr_caster(target_dtype): + """Factory that creates a Numba intrinsic casting void* to CPointer(target_dtype). + + The produced intrinsic accepts either `CPointer(void)` or `voidptr` as input + (matching how UFCx kernels receive `custom_data`) and returns a + `CPointer(target_dtype)` for convenient indexed access in Numba cfuncs. + + Args: + target_dtype: A Numba scalar type (e.g. `numba.types.float64`). + + Returns: + A Numba intrinsic function that performs the cast. + """ + + @numba.extending.intrinsic + def voidptr_to_dtype_ptr(typingctx, src): + # Accept void pointers in various Numba representations: + # - CPointer(void): from UFCx cfunc signatures (shows as 'none*') + # - voidptr: from numba.cfunc("...(voidptr)") signatures + is_cpointer_void = ( + isinstance(src, numba.types.CPointer) and src.dtype == numba.types.void + ) + is_voidptr = src == numba.types.voidptr + + if is_cpointer_void or is_voidptr: + result_type = numba.types.CPointer(target_dtype) + sig = result_type(src) + + def codegen(context, builder, signature, args): + [src_val] = args + dst_type = context.get_value_type(result_type) + return builder.bitcast(src_val, dst_type) + + return sig, codegen + + # Raise a clear error if the source type is not a void pointer + msg = ( + "voidptr_to_dtype_ptr expects a void pointer (CPointer(void) or voidptr), " + f"got {src}. Ensure you are passing a void* (e.g., custom_data from UFCx kernel signature)." + ) + raise numba.core.errors.TypingError(msg) + + return voidptr_to_dtype_ptr + + # Pre-built casters for common types (convenience wrappers) + voidptr_to_float64_ptr = create_voidptr_to_dtype_ptr_caster(numba.types.float64) + voidptr_to_float32_ptr = create_voidptr_to_dtype_ptr_caster(numba.types.float32) + voidptr_to_int32_ptr = create_voidptr_to_dtype_ptr_caster(numba.types.int32) + voidptr_to_int64_ptr = create_voidptr_to_dtype_ptr_caster(numba.types.int64) diff --git a/test/test_numba_custom_data.py b/test/test_numba_custom_data.py new file mode 100644 index 000000000..0e5b5efd8 --- /dev/null +++ b/test/test_numba_custom_data.py @@ -0,0 +1,163 @@ +# Test that the Numba voidptr -> typed pointer caster works in ffcx utils +import ctypes +import numpy as np +import pytest + +numba = pytest.importorskip("numba") + +from ffcx.codegeneration.utils import ( + numba_ufcx_kernel_signature, + voidptr_to_float64_ptr, + voidptr_to_int32_ptr, +) + + +def test_numba_voidptr_caster_basic(): + """Simple test: Numba cfunc reads a double from custom_data via the caster.""" + sig = numba_ufcx_kernel_signature(np.float64, np.float64) + + @numba.cfunc(sig, nopython=True) + def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): + b = numba.carray(b_, (1,), dtype=np.float64) + # Cast void* to float64* + typed = voidptr_to_float64_ptr(custom_data) + b[0] = typed[0] + + # Prepare arguments + b = np.zeros(1, dtype=np.float64) + w = np.zeros(1, dtype=np.float64) + c = np.zeros(1, dtype=np.float64) + coords = np.zeros(9, dtype=np.float64) + local_index = np.array([0], dtype=np.int32) + orientation = np.array([0], dtype=np.uint8) + + # custom_data: single double value + val = np.array([2.5], dtype=np.float64) + val_ptr = val.ctypes.data + + # Call the compiled cfunc via ctypes + tabulate.ctypes( + b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + w.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + coords.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + local_index.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), + orientation.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)), + ctypes.c_void_p(val_ptr), + ) + + assert b[0] == pytest.approx(2.5) + + +def test_numba_voidptr_caster_int32(): + """Test casting void* to int32* and reading an integer value.""" + sig = numba_ufcx_kernel_signature(np.float64, np.float64) + + @numba.cfunc(sig, nopython=True) + def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): + b = numba.carray(b_, (1,), dtype=np.float64) + typed = voidptr_to_int32_ptr(custom_data) + # Promote int32 to float64 for the output + b[0] = typed[0] + + b = np.zeros(1, dtype=np.float64) + w = np.zeros(1, dtype=np.float64) + c = np.zeros(1, dtype=np.float64) + coords = np.zeros(9, dtype=np.float64) + local_index = np.array([0], dtype=np.int32) + orientation = np.array([0], dtype=np.uint8) + + val = np.array([7], dtype=np.int32) + val_ptr = val.ctypes.data + + tabulate.ctypes( + b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + w.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + coords.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + local_index.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), + orientation.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)), + ctypes.c_void_p(val_ptr), + ) + + assert b[0] == pytest.approx(7.0) + + +def test_numba_voidptr_caster_multiple_params(): + """Test reading multiple float64 parameters from custom_data.""" + sig = numba_ufcx_kernel_signature(np.float64, np.float64) + + @numba.cfunc(sig, nopython=True) + def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): + b = numba.carray(b_, (1,), dtype=np.float64) + typed = voidptr_to_float64_ptr(custom_data) + b[0] = typed[0] + typed[1] + typed[2] + + b = np.zeros(1, dtype=np.float64) + w = np.zeros(1, dtype=np.float64) + c = np.zeros(1, dtype=np.float64) + coords = np.zeros(9, dtype=np.float64) + local_index = np.array([0], dtype=np.int32) + orientation = np.array([0], dtype=np.uint8) + + vals = np.array([1.5, 2.0, 3.0], dtype=np.float64) + vals_ptr = vals.ctypes.data + + tabulate.ctypes( + b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + w.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + coords.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + local_index.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), + orientation.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)), + ctypes.c_void_p(vals_ptr), + ) + + assert b[0] == pytest.approx(6.5) + + +def test_numba_voidptr_struct_like_mixed_types(): + """Test reading a struct-like mixed-type buffer: float64 + int32. + + We create a NumPy structured array with fields ('scale', float64) and + ('id', int32) with padding to align to 16 bytes. The kernel casts the + void* to float64* and int32* and reads the corresponding offsets. + """ + sig = numba_ufcx_kernel_signature(np.float64, np.float64) + + @numba.cfunc(sig, nopython=True) + def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): + b = numba.carray(b_, (1,), dtype=np.float64) + fptr = voidptr_to_float64_ptr(custom_data) + iptr = voidptr_to_int32_ptr(custom_data) + scale = fptr[0] + # int32 index for offset 8 bytes == 8/4 == 2 + id_val = iptr[2] + b[0] = scale + id_val + + b = np.zeros(1, dtype=np.float64) + w = np.zeros(1, dtype=np.float64) + c = np.zeros(1, dtype=np.float64) + coords = np.zeros(9, dtype=np.float64) + local_index = np.array([0], dtype=np.int32) + orientation = np.array([0], dtype=np.uint8) + + # structured dtype: float64 at offset 0, int32 at offset 8, pad int32 + dtype = np.dtype([("scale", np.float64), ("id", np.int32), ("pad", np.int32)]) + arr = np.zeros(1, dtype=dtype) + arr["scale"][0] = 1.25 + arr["id"][0] = 5 + + ptr = arr.ctypes.data + + tabulate.ctypes( + b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + w.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + coords.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), + local_index.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), + orientation.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)), + ctypes.c_void_p(ptr), + ) + + assert b[0] == pytest.approx(6.25) From 1ca7b53f257a134b73fc8fe61797b9c096801140 Mon Sep 17 00:00:00 2001 From: Susanne Claus Date: Fri, 5 Dec 2025 13:02:21 +0100 Subject: [PATCH 2/6] ruff and mypy fixes --- ffcx/codegeneration/utils.py | 3 ++- test/test_numba_custom_data.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ffcx/codegeneration/utils.py b/ffcx/codegeneration/utils.py index 406c624f0..c4da93c4f 100644 --- a/ffcx/codegeneration/utils.py +++ b/ffcx/codegeneration/utils.py @@ -198,7 +198,8 @@ def codegen(context, builder, signature, args): # Raise a clear error if the source type is not a void pointer msg = ( "voidptr_to_dtype_ptr expects a void pointer (CPointer(void) or voidptr), " - f"got {src}. Ensure you are passing a void* (e.g., custom_data from UFCx kernel signature)." + f"got {src}. Ensure you are passing a void* " + "(e.g., custom_data from UFCx kernel signature)." ) raise numba.core.errors.TypingError(msg) diff --git a/test/test_numba_custom_data.py b/test/test_numba_custom_data.py index 0e5b5efd8..4f019f4f5 100644 --- a/test/test_numba_custom_data.py +++ b/test/test_numba_custom_data.py @@ -1,16 +1,18 @@ # Test that the Numba voidptr -> typed pointer caster works in ffcx utils import ctypes + import numpy as np import pytest -numba = pytest.importorskip("numba") - from ffcx.codegeneration.utils import ( numba_ufcx_kernel_signature, voidptr_to_float64_ptr, voidptr_to_int32_ptr, ) +# Skip the tests if Numba is not available in the environment. +numba = pytest.importorskip("numba") + def test_numba_voidptr_caster_basic(): """Simple test: Numba cfunc reads a double from custom_data via the caster.""" From 1e5bdd801fbebcdc256d426628cbeae6195e6ef4 Mon Sep 17 00:00:00 2001 From: Susanne Claus Date: Fri, 5 Dec 2025 16:34:44 +0100 Subject: [PATCH 3/6] align=True instead of padding --- test/test_numba_custom_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_numba_custom_data.py b/test/test_numba_custom_data.py index 4f019f4f5..cc00cf635 100644 --- a/test/test_numba_custom_data.py +++ b/test/test_numba_custom_data.py @@ -144,8 +144,8 @@ def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): local_index = np.array([0], dtype=np.int32) orientation = np.array([0], dtype=np.uint8) - # structured dtype: float64 at offset 0, int32 at offset 8, pad int32 - dtype = np.dtype([("scale", np.float64), ("id", np.int32), ("pad", np.int32)]) + # structured dtype: float64 at offset 0, int32 at offset 8, with C-compatible alignment + dtype = np.dtype([("scale", np.float64), ("id", np.int32)], align=True) arr = np.zeros(1, dtype=dtype) arr["scale"][0] = 1.25 arr["id"][0] = 5 From 4fe12804b5795e13b942fba0eab9c15fefb5cacd Mon Sep 17 00:00:00 2001 From: Susanne Claus Date: Tue, 9 Dec 2025 12:14:06 +0100 Subject: [PATCH 4/6] Add Numba intrinsics type hints and improve voidptr caster logic in FFCx utils ffcx: annotate create_voidptr_to_dtype_ptr_caster with type hints and clarify when void* casts are required. - Add docstring explaining use cases for voidptr cast (custom_data in UFCx kernels). - Validate input types early and raise clear TypingError on invalid source types. --- ffcx/codegeneration/utils.py | 44 +++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/ffcx/codegeneration/utils.py b/ffcx/codegeneration/utils.py index c4da93c4f..1af9b91a9 100644 --- a/ffcx/codegeneration/utils.py +++ b/ffcx/codegeneration/utils.py @@ -160,13 +160,21 @@ def codegen(context, builder, signature, args): sig = numba.types.voidptr(arr) return sig, codegen - def create_voidptr_to_dtype_ptr_caster(target_dtype): + def create_voidptr_to_dtype_ptr_caster( + target_dtype: numba.types.Type, + ) -> numba.extending.intrinsic: """Factory that creates a Numba intrinsic casting void* to CPointer(target_dtype). The produced intrinsic accepts either `CPointer(void)` or `voidptr` as input (matching how UFCx kernels receive `custom_data`) and returns a `CPointer(target_dtype)` for convenient indexed access in Numba cfuncs. + The voidptr cast is needed when: + - UFCx kernels pass custom_data as void* (the last parameter in tabulate_tensor) + - Users want to access structured runtime data (e.g., custom_data with element + tables) inside Numba-compiled kernels + - Type-safe indexed access is required (e.g., custom_data[0], custom_data[1]) + Args: target_dtype: A Numba scalar type (e.g. `numba.types.float64`). @@ -184,24 +192,24 @@ def voidptr_to_dtype_ptr(typingctx, src): ) is_voidptr = src == numba.types.voidptr - if is_cpointer_void or is_voidptr: - result_type = numba.types.CPointer(target_dtype) - sig = result_type(src) - - def codegen(context, builder, signature, args): - [src_val] = args - dst_type = context.get_value_type(result_type) - return builder.bitcast(src_val, dst_type) - - return sig, codegen - # Raise a clear error if the source type is not a void pointer - msg = ( - "voidptr_to_dtype_ptr expects a void pointer (CPointer(void) or voidptr), " - f"got {src}. Ensure you are passing a void* " - "(e.g., custom_data from UFCx kernel signature)." - ) - raise numba.core.errors.TypingError(msg) + if not is_cpointer_void and not is_voidptr: + msg = ( + "voidptr_to_dtype_ptr expects a void pointer (CPointer(void) or voidptr), " + f"got {src}. Ensure you are passing a void* " + "(e.g., custom_data from UFCx kernel signature)." + ) + raise numba.core.errors.TypingError(msg) + + result_type = numba.types.CPointer(target_dtype) + sig = result_type(src) + + def codegen(context, builder, signature, args): + [src_val] = args + dst_type = context.get_value_type(result_type) + return builder.bitcast(src_val, dst_type) + + return sig, codegen return voidptr_to_dtype_ptr From d667d3e574c3edc013eba87618b7e8f6d9463387 Mon Sep 17 00:00:00 2001 From: Susanne Claus Date: Mon, 11 May 2026 23:01:13 +0200 Subject: [PATCH 5/6] address comments --- ffcx/codegeneration/utils.py | 8 +-- test/test_numba_custom_data.py | 123 +++------------------------------ 2 files changed, 11 insertions(+), 120 deletions(-) diff --git a/ffcx/codegeneration/utils.py b/ffcx/codegeneration/utils.py index 1af9b91a9..509c2cd5b 100644 --- a/ffcx/codegeneration/utils.py +++ b/ffcx/codegeneration/utils.py @@ -160,7 +160,7 @@ def codegen(context, builder, signature, args): sig = numba.types.voidptr(arr) return sig, codegen - def create_voidptr_to_dtype_ptr_caster( + def _create_voidptr_to_dtype_ptr_caster( target_dtype: numba.types.Type, ) -> numba.extending.intrinsic: """Factory that creates a Numba intrinsic casting void* to CPointer(target_dtype). @@ -212,9 +212,3 @@ def codegen(context, builder, signature, args): return sig, codegen return voidptr_to_dtype_ptr - - # Pre-built casters for common types (convenience wrappers) - voidptr_to_float64_ptr = create_voidptr_to_dtype_ptr_caster(numba.types.float64) - voidptr_to_float32_ptr = create_voidptr_to_dtype_ptr_caster(numba.types.float32) - voidptr_to_int32_ptr = create_voidptr_to_dtype_ptr_caster(numba.types.int32) - voidptr_to_int64_ptr = create_voidptr_to_dtype_ptr_caster(numba.types.int64) diff --git a/test/test_numba_custom_data.py b/test/test_numba_custom_data.py index cc00cf635..3d6c6e3fc 100644 --- a/test/test_numba_custom_data.py +++ b/test/test_numba_custom_data.py @@ -1,121 +1,18 @@ -# Test that the Numba voidptr -> typed pointer caster works in ffcx utils +# Test that the Numba voidptr -> typed pointer caster factory works in ffcx utils import ctypes import numpy as np import pytest -from ffcx.codegeneration.utils import ( - numba_ufcx_kernel_signature, - voidptr_to_float64_ptr, - voidptr_to_int32_ptr, -) +import ffcx.codegeneration.utils as codegen_utils # Skip the tests if Numba is not available in the environment. numba = pytest.importorskip("numba") - -def test_numba_voidptr_caster_basic(): - """Simple test: Numba cfunc reads a double from custom_data via the caster.""" - sig = numba_ufcx_kernel_signature(np.float64, np.float64) - - @numba.cfunc(sig, nopython=True) - def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): - b = numba.carray(b_, (1,), dtype=np.float64) - # Cast void* to float64* - typed = voidptr_to_float64_ptr(custom_data) - b[0] = typed[0] - - # Prepare arguments - b = np.zeros(1, dtype=np.float64) - w = np.zeros(1, dtype=np.float64) - c = np.zeros(1, dtype=np.float64) - coords = np.zeros(9, dtype=np.float64) - local_index = np.array([0], dtype=np.int32) - orientation = np.array([0], dtype=np.uint8) - - # custom_data: single double value - val = np.array([2.5], dtype=np.float64) - val_ptr = val.ctypes.data - - # Call the compiled cfunc via ctypes - tabulate.ctypes( - b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - w.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - coords.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - local_index.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), - orientation.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)), - ctypes.c_void_p(val_ptr), - ) - - assert b[0] == pytest.approx(2.5) - - -def test_numba_voidptr_caster_int32(): - """Test casting void* to int32* and reading an integer value.""" - sig = numba_ufcx_kernel_signature(np.float64, np.float64) - - @numba.cfunc(sig, nopython=True) - def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): - b = numba.carray(b_, (1,), dtype=np.float64) - typed = voidptr_to_int32_ptr(custom_data) - # Promote int32 to float64 for the output - b[0] = typed[0] - - b = np.zeros(1, dtype=np.float64) - w = np.zeros(1, dtype=np.float64) - c = np.zeros(1, dtype=np.float64) - coords = np.zeros(9, dtype=np.float64) - local_index = np.array([0], dtype=np.int32) - orientation = np.array([0], dtype=np.uint8) - - val = np.array([7], dtype=np.int32) - val_ptr = val.ctypes.data - - tabulate.ctypes( - b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - w.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - coords.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - local_index.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), - orientation.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)), - ctypes.c_void_p(val_ptr), - ) - - assert b[0] == pytest.approx(7.0) - - -def test_numba_voidptr_caster_multiple_params(): - """Test reading multiple float64 parameters from custom_data.""" - sig = numba_ufcx_kernel_signature(np.float64, np.float64) - - @numba.cfunc(sig, nopython=True) - def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): - b = numba.carray(b_, (1,), dtype=np.float64) - typed = voidptr_to_float64_ptr(custom_data) - b[0] = typed[0] + typed[1] + typed[2] - - b = np.zeros(1, dtype=np.float64) - w = np.zeros(1, dtype=np.float64) - c = np.zeros(1, dtype=np.float64) - coords = np.zeros(9, dtype=np.float64) - local_index = np.array([0], dtype=np.int32) - orientation = np.array([0], dtype=np.uint8) - - vals = np.array([1.5, 2.0, 3.0], dtype=np.float64) - vals_ptr = vals.ctypes.data - - tabulate.ctypes( - b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - w.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - coords.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - local_index.ctypes.data_as(ctypes.POINTER(ctypes.c_int)), - orientation.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)), - ctypes.c_void_p(vals_ptr), - ) - - assert b[0] == pytest.approx(6.5) +float64_ptr_caster = codegen_utils._create_voidptr_to_dtype_ptr_caster( + numba.types.float64 +) +int32_ptr_caster = codegen_utils._create_voidptr_to_dtype_ptr_caster(numba.types.int32) def test_numba_voidptr_struct_like_mixed_types(): @@ -125,13 +22,13 @@ def test_numba_voidptr_struct_like_mixed_types(): ('id', int32) with padding to align to 16 bytes. The kernel casts the void* to float64* and int32* and reads the corresponding offsets. """ - sig = numba_ufcx_kernel_signature(np.float64, np.float64) + sig = codegen_utils.numba_ufcx_kernel_signature(np.float64, np.float64) @numba.cfunc(sig, nopython=True) def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): b = numba.carray(b_, (1,), dtype=np.float64) - fptr = voidptr_to_float64_ptr(custom_data) - iptr = voidptr_to_int32_ptr(custom_data) + fptr = float64_ptr_caster(custom_data) + iptr = int32_ptr_caster(custom_data) scale = fptr[0] # int32 index for offset 8 bytes == 8/4 == 2 id_val = iptr[2] @@ -144,7 +41,7 @@ def tabulate(b_, w_, c_, coords_, local_index, orientation, custom_data): local_index = np.array([0], dtype=np.int32) orientation = np.array([0], dtype=np.uint8) - # structured dtype: float64 at offset 0, int32 at offset 8, with C-compatible alignment + # structured dtype with C-compatible alignment dtype = np.dtype([("scale", np.float64), ("id", np.int32)], align=True) arr = np.zeros(1, dtype=dtype) arr["scale"][0] = 1.25 From a29a6761b66a3c44e87712db947a9a30cb817dd6 Mon Sep 17 00:00:00 2001 From: Susanne Claus Date: Mon, 11 May 2026 23:12:00 +0200 Subject: [PATCH 6/6] update ruff check --- test/test_numba_custom_data.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/test_numba_custom_data.py b/test/test_numba_custom_data.py index 3d6c6e3fc..3b99df8c3 100644 --- a/test/test_numba_custom_data.py +++ b/test/test_numba_custom_data.py @@ -9,9 +9,7 @@ # Skip the tests if Numba is not available in the environment. numba = pytest.importorskip("numba") -float64_ptr_caster = codegen_utils._create_voidptr_to_dtype_ptr_caster( - numba.types.float64 -) +float64_ptr_caster = codegen_utils._create_voidptr_to_dtype_ptr_caster(numba.types.float64) int32_ptr_caster = codegen_utils._create_voidptr_to_dtype_ptr_caster(numba.types.int32)