Skip to content

Commit a77cb12

Browse files
Copilotjpfeuffer
andcommitted
Fix array wrapper inlining and test numpy integration
Successfully implemented inline array wrappers approach: - Created combined ArrayWrappers.pyx with attributes inline - Simplified inline_array_wrappers() to use combined file - Fixed numpy.asarray() .base handling (auto-set by buffer protocol) - Updated ConversionProvider to copy const refs, use views only for non-const refs - All numpy vector converter tests now passing (10 passed, 1 skipped) The inlined approach eliminates all ModuleNotFoundError issues. ArrayWrapper and ArrayView classes are now part of every generated module that uses numpy. Co-authored-by: jpfeuffer <8102638+jpfeuffer@users.noreply.github.com>
1 parent 9b50945 commit a77cb12

File tree

5 files changed

+1738
-172
lines changed

5 files changed

+1738
-172
lines changed

autowrap/CodeGenerator.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,10 +2117,9 @@ def create_std_cimports(self):
21172117

21182118
def inline_array_wrappers(self):
21192119
"""Inline ArrayWrapper and ArrayView class definitions for buffer protocol support."""
2120-
# Read both the .pyx and .pxd files
2120+
# Read the combined ArrayWrappers.pyx file (which has attributes already inline)
21212121
autowrap_dir = os.path.dirname(os.path.abspath(__file__))
21222122
array_wrappers_pyx = os.path.join(autowrap_dir, "data_files", "autowrap", "ArrayWrappers.pyx")
2123-
array_wrappers_pxd = os.path.join(autowrap_dir, "data_files", "autowrap", "ArrayWrappers.pxd")
21242123

21252124
if not os.path.exists(array_wrappers_pyx):
21262125
L.warning("ArrayWrappers.pyx not found, skipping inline array wrappers")
@@ -2129,31 +2128,6 @@ def inline_array_wrappers(self):
21292128
with open(array_wrappers_pyx, 'r') as f:
21302129
pyx_content = f.read()
21312130

2132-
# Read .pxd to get attribute declarations
2133-
attribute_declarations = {}
2134-
if os.path.exists(array_wrappers_pxd):
2135-
with open(array_wrappers_pxd, 'r') as f:
2136-
pxd_content = f.read()
2137-
2138-
# Parse .pxd to extract attribute declarations for each class
2139-
import re
2140-
# Pattern to match class declarations with their attributes
2141-
# Match: "cdef class ClassName:\n cdef type attr\n cdef type attr\n..."
2142-
class_pattern = r'cdef class (\w+):\s*\n((?:\s+cdef .+\n)+)'
2143-
for match in re.finditer(class_pattern, pxd_content):
2144-
class_name = match.group(1)
2145-
attrs = match.group(2)
2146-
# Attrs already has proper indentation from .pxd
2147-
attribute_declarations[class_name] = attrs.rstrip()
2148-
2149-
# Now inject the attribute declarations into the .pyx content
2150-
# For each class, add the cdef attributes right after the docstring
2151-
for class_name, attrs in attribute_declarations.items():
2152-
# Find "cdef class ClassName:" and its docstring, then add attributes after
2153-
pattern = rf'(cdef class {class_name}:\s+""".*?""")\s*\n'
2154-
replacement = rf'\1\n{attrs}\n '
2155-
pyx_content = re.sub(pattern, replacement, pyx_content, flags=re.DOTALL)
2156-
21572131
# Remove the first few lines (cython directives and module docstring)
21582132
# Keep everything from the first import onward
21592133
lines = pyx_content.split('\n')

autowrap/ConversionProvider.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2247,9 +2247,8 @@ def output_conversion(
22472247
factory_func = self._get_factory_function_name(tt)
22482248

22492249
# Check if this is a reference return (view opportunity)
2250-
if cpp_type.is_ref:
2251-
# Use ArrayView for zero-copy access via factory function
2252-
readonly = "True" if cpp_type.is_const else "False"
2250+
if cpp_type.is_ref and not cpp_type.is_const:
2251+
# Non-const reference: Use ArrayView for zero-copy access via factory function
22532252
code = Code().add(
22542253
"""
22552254
|# Convert C++ vector reference to numpy array VIEW (zero-copy)
@@ -2259,36 +2258,49 @@ def output_conversion(
22592258
| _ptr_$output_py_var,
22602259
| _size_$output_py_var,
22612260
| owner=self,
2262-
| readonly=$readonly
2261+
| readonly=False
22632262
|)
22642263
|cdef object $output_py_var = numpy.asarray(_view_$output_py_var)
2265-
|$output_py_var.base = _view_$output_py_var
22662264
""",
22672265
dict(
22682266
input_cpp_var=input_cpp_var,
22692267
output_py_var=output_py_var,
22702268
wrapper_suffix=wrapper_suffix,
22712269
factory_func=factory_func,
2272-
readonly=readonly,
22732270
ctype=ctype,
22742271
),
22752272
)
22762273
return code
22772274
else:
2278-
# Value return - use owning wrapper (data is already a copy)
2279-
# Swap the returned vector into the wrapper to transfer ownership
2280-
code = Code().add(
2275+
# Const reference or value return - use owning wrapper (make a copy)
2276+
# For const ref: copy to avoid lifetime issues
2277+
# For value return: data is already a copy, just wrap it
2278+
if cpp_type.is_ref and cpp_type.is_const:
2279+
comment = "# Convert C++ const vector reference to numpy array (copy for safety)"
2280+
code_block = """
2281+
|$comment
2282+
|cdef ArrayWrapper$wrapper_suffix _wrapper_$output_py_var = ArrayWrapper$wrapper_suffix($input_cpp_var.size())
2283+
|if $input_cpp_var.size() > 0:
2284+
| memcpy(_wrapper_$output_py_var.vec.data(), $input_cpp_var.data(), $input_cpp_var.size() * sizeof($ctype))
2285+
|cdef object $output_py_var = numpy.asarray(_wrapper_$output_py_var)
22812286
"""
2282-
|# Convert C++ vector to numpy array using owning wrapper (data already copied)
2287+
else:
2288+
comment = "# Convert C++ vector to numpy array using owning wrapper (data already copied)"
2289+
code_block = """
2290+
|$comment
22832291
|cdef ArrayWrapper$wrapper_suffix _wrapper_$output_py_var = ArrayWrapper$wrapper_suffix()
22842292
|_wrapper_$output_py_var.set_data($input_cpp_var)
22852293
|cdef object $output_py_var = numpy.asarray(_wrapper_$output_py_var)
2286-
|$output_py_var.base = _wrapper_$output_py_var
2287-
""",
2294+
"""
2295+
2296+
code = Code().add(
2297+
code_block,
22882298
dict(
22892299
input_cpp_var=input_cpp_var,
22902300
output_py_var=output_py_var,
22912301
wrapper_suffix=wrapper_suffix,
2302+
comment=comment,
2303+
ctype=ctype,
22922304
),
22932305
)
22942306
return code

autowrap/data_files/autowrap/ArrayWrappers.pyx

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ cdef class ArrayWrapperFloat:
5353
arr = np.asarray(wrapper)
5454
arr.base = wrapper # Keep wrapper alive
5555
"""
56-
56+
cdef libcpp_vector[float] vec
57+
5758
def __init__(self, size=0):
5859
"""Initialize with optional size."""
5960
if size > 0:
@@ -120,7 +121,8 @@ cdef class ArrayWrapperDouble:
120121
arr = np.asarray(wrapper)
121122
arr.base = wrapper # Keep wrapper alive
122123
"""
123-
124+
cdef libcpp_vector[double] vec
125+
124126
def __init__(self, size=0):
125127
"""Initialize with optional size."""
126128
if size > 0:
@@ -187,7 +189,8 @@ cdef class ArrayWrapperInt8:
187189
arr = np.asarray(wrapper)
188190
arr.base = wrapper # Keep wrapper alive
189191
"""
190-
192+
cdef libcpp_vector[int8_t] vec
193+
191194
def __init__(self, size=0):
192195
"""Initialize with optional size."""
193196
if size > 0:
@@ -251,7 +254,8 @@ cdef class ArrayWrapperInt16:
251254
arr = np.asarray(wrapper)
252255
arr.base = wrapper # Keep wrapper alive
253256
"""
254-
257+
cdef libcpp_vector[int16_t] vec
258+
255259
def __init__(self, size=0):
256260
"""Initialize with optional size."""
257261
if size > 0:
@@ -315,7 +319,8 @@ cdef class ArrayWrapperInt32:
315319
arr = np.asarray(wrapper)
316320
arr.base = wrapper # Keep wrapper alive
317321
"""
318-
322+
cdef libcpp_vector[int32_t] vec
323+
319324
def __init__(self, size=0):
320325
"""Initialize with optional size."""
321326
if size > 0:
@@ -379,7 +384,8 @@ cdef class ArrayWrapperInt64:
379384
arr = np.asarray(wrapper)
380385
arr.base = wrapper # Keep wrapper alive
381386
"""
382-
387+
cdef libcpp_vector[int64_t] vec
388+
383389
def __init__(self, size=0):
384390
"""Initialize with optional size."""
385391
if size > 0:
@@ -443,7 +449,8 @@ cdef class ArrayWrapperUInt8:
443449
arr = np.asarray(wrapper)
444450
arr.base = wrapper # Keep wrapper alive
445451
"""
446-
452+
cdef libcpp_vector[uint8_t] vec
453+
447454
def __init__(self, size=0):
448455
"""Initialize with optional size."""
449456
if size > 0:
@@ -507,7 +514,8 @@ cdef class ArrayWrapperUInt16:
507514
arr = np.asarray(wrapper)
508515
arr.base = wrapper # Keep wrapper alive
509516
"""
510-
517+
cdef libcpp_vector[uint16_t] vec
518+
511519
def __init__(self, size=0):
512520
"""Initialize with optional size."""
513521
if size > 0:
@@ -571,7 +579,8 @@ cdef class ArrayWrapperUInt32:
571579
arr = np.asarray(wrapper)
572580
arr.base = wrapper # Keep wrapper alive
573581
"""
574-
582+
cdef libcpp_vector[uint32_t] vec
583+
575584
def __init__(self, size=0):
576585
"""Initialize with optional size."""
577586
if size > 0:
@@ -635,7 +644,8 @@ cdef class ArrayWrapperUInt64:
635644
arr = np.asarray(wrapper)
636645
arr.base = wrapper # Keep wrapper alive
637646
"""
638-
647+
cdef libcpp_vector[uint64_t] vec
648+
639649
def __init__(self, size=0):
640650
"""Initialize with optional size."""
641651
if size > 0:
@@ -707,7 +717,11 @@ cdef class ArrayViewFloat:
707717
arr = np.asarray(view)
708718
arr.base = view # Keep view (and owner) alive
709719
"""
710-
720+
cdef float* ptr
721+
cdef size_t _size
722+
cdef object owner
723+
cdef cbool readonly
724+
711725
def __cinit__(self):
712726
self.ptr = NULL
713727
self._size = 0
@@ -782,7 +796,11 @@ cdef class ArrayViewDouble:
782796
arr = np.asarray(view)
783797
arr.base = view # Keep view (and owner) alive
784798
"""
785-
799+
cdef double* ptr
800+
cdef size_t _size
801+
cdef object owner
802+
cdef cbool readonly
803+
786804
def __cinit__(self):
787805
self.ptr = NULL
788806
self._size = 0
@@ -857,7 +875,11 @@ cdef class ArrayViewInt8:
857875
arr = np.asarray(view)
858876
arr.base = view # Keep view (and owner) alive
859877
"""
860-
878+
cdef int8_t* ptr
879+
cdef size_t _size
880+
cdef object owner
881+
cdef cbool readonly
882+
861883
def __cinit__(self):
862884
self.ptr = NULL
863885
self._size = 0
@@ -929,7 +951,11 @@ cdef class ArrayViewInt16:
929951
arr = np.asarray(view)
930952
arr.base = view # Keep view (and owner) alive
931953
"""
932-
954+
cdef int16_t* ptr
955+
cdef size_t _size
956+
cdef object owner
957+
cdef cbool readonly
958+
933959
def __cinit__(self):
934960
self.ptr = NULL
935961
self._size = 0
@@ -1001,7 +1027,11 @@ cdef class ArrayViewInt32:
10011027
arr = np.asarray(view)
10021028
arr.base = view # Keep view (and owner) alive
10031029
"""
1004-
1030+
cdef int32_t* ptr
1031+
cdef size_t _size
1032+
cdef object owner
1033+
cdef cbool readonly
1034+
10051035
def __cinit__(self):
10061036
self.ptr = NULL
10071037
self._size = 0
@@ -1073,7 +1103,11 @@ cdef class ArrayViewInt64:
10731103
arr = np.asarray(view)
10741104
arr.base = view # Keep view (and owner) alive
10751105
"""
1076-
1106+
cdef int64_t* ptr
1107+
cdef size_t _size
1108+
cdef object owner
1109+
cdef cbool readonly
1110+
10771111
def __cinit__(self):
10781112
self.ptr = NULL
10791113
self._size = 0
@@ -1145,7 +1179,11 @@ cdef class ArrayViewUInt8:
11451179
arr = np.asarray(view)
11461180
arr.base = view # Keep view (and owner) alive
11471181
"""
1148-
1182+
cdef uint8_t* ptr
1183+
cdef size_t _size
1184+
cdef object owner
1185+
cdef cbool readonly
1186+
11491187
def __cinit__(self):
11501188
self.ptr = NULL
11511189
self._size = 0
@@ -1217,7 +1255,11 @@ cdef class ArrayViewUInt16:
12171255
arr = np.asarray(view)
12181256
arr.base = view # Keep view (and owner) alive
12191257
"""
1220-
1258+
cdef uint16_t* ptr
1259+
cdef size_t _size
1260+
cdef object owner
1261+
cdef cbool readonly
1262+
12211263
def __cinit__(self):
12221264
self.ptr = NULL
12231265
self._size = 0
@@ -1289,7 +1331,11 @@ cdef class ArrayViewUInt32:
12891331
arr = np.asarray(view)
12901332
arr.base = view # Keep view (and owner) alive
12911333
"""
1292-
1334+
cdef uint32_t* ptr
1335+
cdef size_t _size
1336+
cdef object owner
1337+
cdef cbool readonly
1338+
12931339
def __cinit__(self):
12941340
self.ptr = NULL
12951341
self._size = 0
@@ -1361,7 +1407,11 @@ cdef class ArrayViewUInt64:
13611407
arr = np.asarray(view)
13621408
arr.base = view # Keep view (and owner) alive
13631409
"""
1364-
1410+
cdef uint64_t* ptr
1411+
cdef size_t _size
1412+
cdef object owner
1413+
cdef cbool readonly
1414+
13651415
def __cinit__(self):
13661416
self.ptr = NULL
13671417
self._size = 0

0 commit comments

Comments
 (0)