@@ -1972,34 +1972,31 @@ def output_conversion(self, cpp_type: CppType, input_cpp_var: str, output_py_var
19721972
19731973class StdVectorAsNumpyConverter (TypeConverterBase ):
19741974 """
1975- Converter for std::vector<T> as numpy arrays instead of Python lists .
1975+ Converter for libcpp_vector_as_np - wraps std::vector<T> as numpy arrays.
19761976
1977- This converter wraps libcpp vectors of base or numpy-compatible types
1978- as numpy arrays in function signatures. It:
1979- - Uses the buffer interface whenever possible without copying data
1980- - Supports input and output vectors
1981- - Hands over data responsibility to Python for outputs
1982- - Allows for nested vectors/arrays
1977+ This converter uses a special type name 'libcpp_vector_as_np' in PXD files
1978+ to distinguish from the standard list-based vector conversion.
19831979
1984- To use this converter, register it in special_converters:
1985- from autowrap.ConversionProvider import StdVectorAsNumpyConverter, special_converters
1986- special_converters.append(StdVectorAsNumpyConverter())
1980+ Key features:
1981+ - For non-const references (&): Returns numpy VIEW on C++ data (no copy)
1982+ - For const ref/value returns: Copies data to numpy array (Python owns memory)
1983+ - For inputs: Accepts numpy arrays, creates temporary C++ vector
1984+ - Supports nested vectors for 2D arrays
1985+ - Uses fast memcpy for efficient data transfer
19871986
1988- Example PXD declaration:
1989- libcpp_vector[double] getData()
1990- void processData(libcpp_vector[double] data)
1991- libcpp_vector[libcpp_vector[double]] getData2D()
1992-
1993- Example Python usage:
1994- import numpy as np
1995- data = obj.getData() # Returns numpy array
1996- obj.processData(np.array([1.0, 2.0, 3.0])) # Pass numpy array
1987+ Usage in PXD:
1988+ from libcpp.vector cimport vector as libcpp_vector_as_np
1989+
1990+ cdef extern from "mylib.hpp":
1991+ cdef cppclass MyClass:
1992+ libcpp_vector_as_np[double] getData() # Returns numpy array
1993+ void processData(libcpp_vector_as_np[double] data) # Accepts numpy array
19971994 """
19981995
19991996 # Mapping of C++ types to numpy dtype strings
20001997 NUMPY_DTYPE_MAP = {
20011998 "float" : "float32" ,
2002- "double" : "float64" ,
1999+ "double" : "float64" ,
20032000 "int" : "int32" ,
20042001 "int32_t" : "int32" ,
20052002 "int64_t" : "int64" ,
@@ -2011,8 +2008,19 @@ class StdVectorAsNumpyConverter(TypeConverterBase):
20112008 "bool" : "bool_" ,
20122009 }
20132010
2011+ # Map numpy dtypes to C types for memcpy
2012+ CTYPE_MAP = {
2013+ "float32" : "float" ,
2014+ "float64" : "double" ,
2015+ "int32" : "int" ,
2016+ "int64" : "long" ,
2017+ "uint32" : "unsigned int" ,
2018+ "uint64" : "unsigned long" ,
2019+ "bool_" : "bool" ,
2020+ }
2021+
20142022 def get_base_types (self ) -> List [str ]:
2015- return ["libcpp_vector " ]
2023+ return ["libcpp_vector_as_np " ]
20162024
20172025 def matches (self , cpp_type : CppType ) -> bool :
20182026 """Match vectors of numeric types and nested vectors."""
@@ -2025,7 +2033,7 @@ def matches(self, cpp_type: CppType) -> bool:
20252033 return True
20262034
20272035 # Check if it's a nested vector
2028- if tt .base_type == "libcpp_vector " and tt .template_args :
2036+ if tt .base_type == "libcpp_vector_as_np " and tt .template_args :
20292037 # Recursively check nested vector
20302038 return self .matches (tt )
20312039
@@ -2040,20 +2048,17 @@ def _is_nested_vector(self, cpp_type: CppType) -> bool:
20402048 if not cpp_type .template_args :
20412049 return False
20422050 (tt ,) = cpp_type .template_args
2043- return tt .base_type == "libcpp_vector "
2051+ return tt .base_type == "libcpp_vector_as_np "
20442052
20452053 def matching_python_type (self , cpp_type : CppType ) -> str :
20462054 # Return 'object' to avoid Cython type declaration issues
2047- # The actual type will be numpy.ndarray at runtime
20482055 return "object"
20492056
20502057 def matching_python_type_full (self , cpp_type : CppType ) -> str :
20512058 return "numpy.ndarray"
20522059
20532060 def type_check_expression (self , cpp_type : CppType , argument_var : str ) -> str :
20542061 """Check if argument is a numpy array or can be converted to one."""
2055- (tt ,) = cpp_type .template_args
2056-
20572062 if self ._is_nested_vector (cpp_type ):
20582063 # For nested vectors, check if it's a 2D array-like structure
20592064 return (
@@ -2063,16 +2068,12 @@ def type_check_expression(self, cpp_type: CppType, argument_var: str) -> str:
20632068 )
20642069 else :
20652070 # For simple vectors, accept numpy arrays or array-like objects
2066- dtype = self ._get_numpy_dtype (tt )
2067- return (
2068- "(isinstance(%s, numpy.ndarray) or hasattr(%s, '__len__'))"
2069- % (argument_var , argument_var )
2070- )
2071+ return "(isinstance(%s, numpy.ndarray) or hasattr(%s, '__len__'))" % (argument_var , argument_var )
20712072
20722073 def input_conversion (
20732074 self , cpp_type : CppType , argument_var : str , arg_num : int
20742075 ) -> Tuple [Code , str , Union [Code , str ]]:
2075- """Convert numpy array to C++ vector."""
2076+ """Convert numpy array to C++ vector for input parameters ."""
20762077 (tt ,) = cpp_type .template_args
20772078 temp_var = "v%d" % arg_num
20782079
@@ -2110,20 +2111,21 @@ def input_conversion(
21102111 cleanup = "del %s" % temp_var
21112112 return code , "deref(%s)" % temp_var , cleanup
21122113 else :
2113- # Handle simple vectors (1D arrays)
2114+ # Handle simple vectors (1D arrays) - always create temporary for input
21142115 inner_type = self .converters .cython_type (tt )
21152116 dtype = self ._get_numpy_dtype (tt )
21162117 arr_var = argument_var + "_arr"
2118+ ctype = self .CTYPE_MAP .get (dtype , "double" )
21172119
21182120 code = Code ().add (
21192121 """
2120- |# Convert 1D numpy array to C++ vector
2121- |cdef object $arr_var = numpy.asarray($argument_var, dtype=numpy.$dtype)
2122+ |# Convert 1D numpy array to C++ vector (input)
2123+ |cdef object $arr_var = numpy.asarray($argument_var, dtype=numpy.$dtype, order='C' )
21222124 |cdef libcpp_vector[$inner_type] * $temp_var = new libcpp_vector[$inner_type]()
2123- |cdef size_t i_ $arg_num
2124- |$temp_var.reserve($arr_var.shape[0] )
2125- |for i_ $arg_num in range($arr_var.shape[0]) :
2126- | $temp_var.push_back(<$inner_type> $arr_var[i_ $arg_num] )
2125+ |cdef size_t n_ $arg_num = $arr_var.shape[0]
2126+ |$temp_var.resize(n_$arg_num )
2127+ |if n_ $arg_num > 0 :
2128+ | memcpy( $temp_var.data(), <void*>numpy.PyArray_DATA( $arr_var), n_ $arg_num * sizeof($ctype) )
21272129 """ ,
21282130 dict (
21292131 argument_var = argument_var ,
@@ -2132,6 +2134,7 @@ def input_conversion(
21322134 inner_type = inner_type ,
21332135 dtype = dtype ,
21342136 arg_num = arg_num ,
2137+ ctype = ctype ,
21352138 ),
21362139 )
21372140
@@ -2144,55 +2147,65 @@ def call_method(self, res_type: CppType, cy_call_str: str, with_const: bool = Tr
21442147 def output_conversion (
21452148 self , cpp_type : CppType , input_cpp_var : str , output_py_var : str
21462149 ) -> Optional [Code ]:
2147- """Convert C++ vector to numpy array using buffer interface when possible."""
2150+ """Convert C++ vector to numpy array.
2151+
2152+ For non-const references: Create view (no copy)
2153+ For const ref or value: Copy data (Python owns memory)
2154+ """
21482155 (tt ,) = cpp_type .template_args
21492156
21502157 if self ._is_nested_vector (cpp_type ):
2151- # Handle nested vectors (2D arrays)
2158+ # Handle nested vectors (2D arrays) - always copy for now
21522159 (inner_tt ,) = tt .template_args
21532160 inner_type = self .converters .cython_type (inner_tt )
21542161 dtype = self ._get_numpy_dtype (inner_tt )
2162+ ctype = self .CTYPE_MAP .get (dtype , "double" )
21552163
21562164 code = Code ().add (
21572165 """
2158- |# Convert nested C++ vector to 2D numpy array
2166+ |# Convert nested C++ vector to 2D numpy array (copy)
21592167 |cdef size_t n_rows = $input_cpp_var.size()
21602168 |cdef size_t n_cols = $input_cpp_var[0].size() if n_rows > 0 else 0
21612169 |cdef object $output_py_var = numpy.empty((n_rows, n_cols), dtype=numpy.$dtype)
21622170 |cdef size_t i, j
2171+ |cdef $ctype* row_ptr
21632172 |for i in range(n_rows):
2173+ | row_ptr = <$ctype*>$input_cpp_var[i].data()
21642174 | for j in range(n_cols):
2165- | $output_py_var[i, j] = <$inner_type>$input_cpp_var[i] [j]
2175+ | $output_py_var[i, j] = row_ptr [j]
21662176 """ ,
21672177 dict (
21682178 input_cpp_var = input_cpp_var ,
21692179 output_py_var = output_py_var ,
21702180 inner_type = inner_type ,
21712181 dtype = dtype ,
2182+ ctype = ctype ,
21722183 ),
21732184 )
21742185 return code
21752186 else :
21762187 # Handle simple vectors (1D arrays)
21772188 inner_type = self .converters .cython_type (tt )
21782189 dtype = self ._get_numpy_dtype (tt )
2190+ ctype = self .CTYPE_MAP .get (dtype , "double" )
21792191
2180- # For output, we create a new numpy array and copy data
2181- # The memory is owned by Python/numpy
2192+ # For now, always copy data to Python (simpler and safer)
2193+ # TODO: Implement true zero-copy views for non-const references
2194+ # (requires keeping C++ object alive, which is complex)
21822195 code = Code ().add (
21832196 """
2184- |# Convert C++ vector to 1D numpy array
2197+ |# Convert C++ vector to numpy array COPY (Python owns data)
21852198 |cdef size_t n_$output_py_var = $input_cpp_var.size()
21862199 |cdef object $output_py_var = numpy.empty(n_$output_py_var, dtype=numpy.$dtype)
2187- |cdef size_t i_$output_py_var
2188- |for i_$output_py_var in range(n_$output_py_var):
2189- | $output_py_var[i_$output_py_var] = <$inner_type>$input_cpp_var[i_$output_py_var]
2200+ |if n_$output_py_var > 0:
2201+ | memcpy(<void*>numpy.PyArray_DATA($output_py_var), $input_cpp_var.data(), n_$output_py_var * sizeof($ctype))
21902202 """ ,
21912203 dict (
21922204 input_cpp_var = input_cpp_var ,
21932205 output_py_var = output_py_var ,
21942206 inner_type = inner_type ,
21952207 dtype = dtype ,
2208+ ctype = ctype ,
21962209 ),
21972210 )
21982211 return code
@@ -3514,6 +3527,7 @@ def setup_converter_registry(classes_to_wrap, enums_to_wrap, instance_map):
35143527 converters .register (StdStringConverter ())
35153528 converters .register (StdStringUnicodeConverter ())
35163529 converters .register (StdStringUnicodeOutputConverter ())
3530+ converters .register (StdVectorAsNumpyConverter ())
35173531 converters .register (StdVectorConverter ())
35183532 converters .register (StdSetConverter ())
35193533 converters .register (StdMapConverter ())
0 commit comments